Invalid identifier value or configuration. api plateform - symfony

I need to reset password user with api plateform. I have create custom endpoint :
itemOperations: [
'sendEmail' => [
'method' => 'patch',
'path' => '/users/password/send/token',
'controller' => ResetPasswordRequestsController::class,
'normalization_context' => ['groups' => [
'read:sendEmail:user:item',
]],
],
]
In my controller search user with email and create token for reset passeword.
When i send my request i got a error : "Invalid identifier value or configuration." :
PATH : http://192.168.0.11:8000/api/users/password/send/token
{
"email": "user#email.com"
}
I think Api plateform need a id of my entity User with PATCH method but i send only email.
User Entty attributs
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
* #Assert\NotBlank(groups={"sendEmail"})
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
* #Assert\NotBlank
*/
private $password;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
private $firstName;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
private $lastName;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #ORM\OneToOne(targetEntity=Teacher::class, mappedBy="user", cascade={"persist", "remove"})
*/
private $teacher;
/**
* #ORM\OneToOne(targetEntity=Student::class, mappedBy="user", cascade={"persist", "remove"})
*/
private $student;
/**
* #ORM\ManyToMany(targetEntity=Role::class, mappedBy="users")
*/
private $dbroles;
/**
* #ORM\OneToMany(targetEntity=Invitation::class, mappedBy="owner")
*/
private $invitations;
/**
* #ORM\OneToMany(targetEntity=Inscription::class, mappedBy="userRegister",cascade={"persist"})
*/
private $inscriptions;
/**
* #ORM\OneToMany(targetEntity=UserInvitation::class, mappedBy="owner")
*/
private $userInvitations;
/**
* #ORM\OneToMany(targetEntity=UserInvitation::class, mappedBy="guest")
*/
private $userInvitationsGuest;
}
my controller :
<?php
namespace App\Controller;
use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ResetPasswordRequestsController extends AbstractController
{
use ResetPasswordControllerTrait;
private $mailer;
private $resetPasswordHelper;
private $passwordEncoder;
public function __construct(ValidatorInterface $validator, MailerInterface $mailer, ResetPasswordHelperInterface $resetPasswordHelper, UserPasswordEncoderInterface $passwordEncoder)
{
$this->mailer = $mailer;
$this->resetPasswordHelper = $resetPasswordHelper;
$this->passwordEncoder = $passwordEncoder;
$this->validator = $validator;
}
public function __invoke(Request $request)
{
$data = $request->get('data');
if($data->getEmail()){
$this->sendEmail($data->getEmail());
}
if($data->getPassword() && $request->get("token")){
$this->resetPassword($data->getPassword(), $request->get("token"));
}
}
public function resetPassword($newPassword, $token){
try {
$user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
} catch (ResetPasswordExceptionInterface $e) {
$this->addFlash('reset_password_error', sprintf(
'There was a problem validating your reset request - %s',
$e->getReason()
));
return $this->redirectToRoute('app_forgot_password_request');
}
$this->resetPasswordHelper->removeResetRequest($token);
$encodedPassword = $this->passwordEncoder->encodePassword(
$user,
$newPassword
);
$user->setPassword($encodedPassword);
$this->getDoctrine()->getManager()->flush();
// The session is cleaned up after the password has been changed.
$this->cleanSessionAfterReset();
return $user;
}
public function sendEmail($email){
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy([
'email' => $email,
]);
if (!$user) {
return throw new Exception(sprintf("L'utilisateur est introuvable"));
}
try {
$resetToken = $this->resetPasswordHelper->generateResetToken($user);
} catch (ResetPasswordExceptionInterface $e) {
return throw new Exception(sprintf("Erreur lors de la génération du token"));
}
$token = $resetToken->getToken();
$url = $this->getParameter('front_app_url');
$redirectUrl = $url . "/auth/reset/" . $token;
$email = (new TemplatedEmail())
->from(new Address('mouchelet.thomas#gmail.com', 'thomas mouchelet'))
->to($user->getEmail())
->subject('Your password reset request')
->htmlTemplate('reset_password/email.html.twig')
->context([
'resetToken' => $resetToken,
'redirectUrl' => $redirectUrl
])
;
$this->mailer->send($email);
$this->setTokenObjectInSession($resetToken);
return $user;
}
}
EDIT 1 : Not working
thx i have try it
collectionOperations: [
'sendEmail' => [
'write' => false,
'serialize' => false,
'method' => 'post',
'path' => '/users/password/send/token',
'controller' => ResetPasswordRequestsController::class,
],
],
i have try with validations_groups :
collectionOperations: [
'post' => ['validation_groups' => ['Default', 'post:user']],
]
#Assert\NotBlank(groups={"post:user"})
but i have error :
Cannot validate values of type \"null\" automatically. Please provide a constraint.

By default, GET, PUT and PATCH methods needs an identifier.
There is several way to achieve what you want.
The first one is to fallback to a regular Symfony controller. Just declare a new POST endpoint instead of PATCH. The counterpart is that this endpoint won't be visible in the OpenAPI view.
The second one is to switch to a POST operation. POST operation does not need an identifier. Because you don't want to save anything, you could either return a Response from your controller to stop the lifecycle, or disable the WriteListener (and perhaps the SerializeListener as well) :
itemOperations: [
'sendEmail' => [
'write' => false,
'serialize' => false,
'method' => 'post',
'path' => '/users/password/send/token',
'controller' => ResetPasswordRequestsController::class,
'normalization_context' => ['groups' => [
'read:sendEmail:user:item',
]],
],
]
The third one looks like the second, keep your PATCH operation, but disable the ReadListener: 'read' => false. The ReadListener is the part which require an identifier. I guess your controller will receive an array containing only the body of your request.

Related

Symfony - Can't get a way to read the property (PropertyAccessor)

I develop with symfony a form for create tickets but when i try i got this error:
my function buildform from the file tickettype.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('Titre', TextType::class)
->add('Message', TextType::class)
->add('Date', DateTimeType::class, ['data' => new \DateTime()] )
->add('Demandeur', EntityType::class, [
'class' => Client::class,
'choice_label' => 'Nom',
])
->add('Agent', EntityType::class, [
'class' => Dealer::class,
'choice_label' => 'Nom',
])
->add('Etat_Ticket', EntityType::class, [
'class' => Etat::class,
'choice_label' => 'Statut',
]);
}
and in the controller :
/**
* #Route("/add/", name="add_ticket")
*
* #param Request $request
* #return \Symfony\Component\HttpFoundation\Response
*/
public function addTicketAction(Request $request)
{
$ticket = new Ticket();
$form = $this->createForm(TicketType::class, $ticket);
$form->add('send', SubmitType::class, ['label' => 'créé un nouveau ticket']);
$form->handleRequest($request);
if($form->isSubmitted()){
$ticket->setDate(new \DateTime());
$em = $this->getDoctrine()->getManager();
$em->persist($ticket);
$em->flush();
return $this->redirectToRoute('List_ticket');
}
return $this->render("add.html.twig", array('form' => $form->createView()));
}
and my entity Ticket have this property:
/**
* #ORM\ManyToOne(targetEntity=Etat::class, inversedBy="Etat_Ticket")
* #ORM\JoinColumn(nullable=false)
*/
private $Etat_Ticket;
link to the entity Etat which look like this :
/**
* Etat
*
* #ORM\Table(name="etat")
* #ORM\Entity
*/
class Etat
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="statut", type="string", length=255, nullable=false)
*/
private $statut;
public function getId(): ?int
{
return $this->id;
}
public function getStatut(): ?string
{
return $this->statut;
}
public function setStatut(string $statut): self
{
$this->statut = $statut;
return $this;
}
}
You don't have a getter for Etat_Ticket property, so symfony (and that's a PHP common rule, when the property is not public) can't read its value from outside class scope. Symfony Form component, here, is trying by default to use the getter or access the property directly if it were public, and as neither getter nor public property are found, it symply doesn't know how to retrieve the value.
You can feed the form by yourself (docs) or use property_path.
Remember also that binding entities (or in general the domain model) directly to a form is good for RAD (rapid application development) but not so good in the long term. I would suggest to use a sort of DTO in order to read and write from and to the model (take a look here in order to get an idea about this concept)

What's the propper way to insert data one to many relation symfony4?

It's simple im doing a permission table from Companies and Users where i have to store the user id and the company id right now i have this and i get this error
Expected value of type "App\Entity\CompanyUserPermissionMap" for association field "App\Entity\User#$companyUserPermissionMaps", got "App\Entity\Company" instead.
User Entity
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $userId;
/**
* #ORM\Column(type="string", length=12)
*/
private $code;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #var CompanyUserPermissionMap[]
* #ORM\OneToMany(targetEntity="App\Entity\CompanyUserPermissionMap", mappedBy="user", orphanRemoval=true)
*/
private $companyUserPermissionMaps;
public function __construct()
{
$this->companyUserPermissionMaps = new ArrayCollection();
}
public function getCompanyUserPermissionMaps(): Collection
{
return $this->companyUserPermissionMaps;
}
public function addCompanyUserPermissionMaps(CompanyUserPermissionMaps $permission): self
{
if (!$this->companyUserPermissionMaps->contains($permission)) {
$this->companyUserPermissionMaps[] = $permission;
$permission->setUser($this);
}
return $this;
}
Company Entity
#########################
## PROPERTIES ##
#########################
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $companyId;
/**
* #ORM\Column(type="string", length=6, unique=true)
*/
private $code;
/**
* #var CompanyUserPermissionMap[]
* #ORM\OneToMany(targetEntity="App\Entity\CompanyUserPermissionMap", mappedBy="company", orphanRemoval=true)
*/
private $companyUserPermissionMaps;
public function __construct()
{
$this->companyUserPermissionMaps = new ArrayCollection();
}
/**
* #return Collection|CompanyUserPermissionMaps[]
*/
public function getCompanyUserPermissionMaps(): Collection
{
return $this->companyUserPermissionMaps;
}
public function addCompanyUserPermissionMaps(AccountingBankPermission $permission): self
{
if (!$this->companyUserPermissionMaps->contains($permission)) {
$this->companyUserPermissionMaps[] = $permission;
$permission->setAccount($this);
}
return $this;
}
public function removeCompanyUserPermissionMaps(AccountingBankPermission $permission): self
{
if ($this->companyUserPermissionMaps->contains($permission)) {
$this->companyUserPermissionMaps->removeElement($permission);
// set the owning side to null (unless already changed)
if ($permission->getAccount() === $this) {
$permission->setAccount(null);
}
}
return $this;
}
relation table
Column(type="integer")
private $companyUserPermissionId;
/**
* #var User
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="companyUserPermissionMaps")
* #ORM\JoinColumn(referencedColumnName="user_id", nullable=false)
*/
private $user;
/**
* #var Company
* #ORM\ManyToOne(targetEntity="App\Entity\Company", inversedBy="companyUserPermissionMaps")
* #ORM\JoinColumn(referencedColumnName="company_id", nullable=true)
*/
private $company;
/**
* #return int|null
*/
public function getCompanyUserPermissionId(): ?int
{
return $this->companyUserPermisionId;
}
/**
* #return User
*/
public function getUser(): ?User
{
return $this->user;
}
/**
* #param User $user
* #return AccountingBankPermission
*/
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
/**
* #return Company
*/
public function getCompany(): ?Company
{
return $this->company;
}
/**
* #param array $company
* #return CompanyUserPermissionMap
*/
public function setCompany(?array $company): self
{
$this->company = $company;
return $this;
}
Form type
$builder
->add('roles' ,ChoiceType::class ,[
'required' => true,
'choices' => $this->roles,
'multiple' => true,
'expanded' => true,
'label_attr' => [
'class' => 'custom-control-label',
],
'choice_attr' => function($val, $key, $index) {
return ['class' => 'custom-control-input'];
},
'attr'=>['class' =>'custom-checkbox custom-control']
])
->add('email', EmailType::class, [
'label' => "E-Mail"
])
->add('firstName', TextType::class, [
'label' => "First Name"
])
->add('lastName', TextType::class, [
'label' => "Last Name"
])
->add('companyUserPermissionMaps' ,EntityType::class ,[
'required' => true,
'class' => Company::class,
'label' => 'Compañia',
'multiple' => true,
'expanded' => false,
'choice_label' => 'legalName',
'mapped'=>false
])
->add('save', SubmitType::class, [
'label' => "Save"
])
and my controller function looks like this
$user = new User();
$originalRoles = $this->getParameter('security.role_hierarchy.roles');
$options=['roles' => $originalRoles ];
$form = $this->createForm(UserType::class, $user, $options);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$tempPassword = "some pass";
$user->setPassword($encoder->encodePassword(
$user,
$tempPassword
));
$companies=[];
$companiesForm=$form->get('companyUserPermissionMaps')->getData();
foreach ($companiesForm as $value) {
$companies[] = $value->getCompanyId();
}
// Save object to database
$entityManager = $this->getDoctrine()->getManager();
/** #var CompanyRepository $companyRepository */
$companyRepository = $this->getDoctrine()->getRepository(Company::class);
$companiesArr =$companyRepository->findCompanyByArray($companies);
$companyUserPermissionMap = new CompanyUserPermissionMap();
$companyUserPermissionMap->setUser($user);
$companyUserPermissionMap->setCompany($companiesArr);
$entityManager->persist($user);
$entityManager->persist($companyUserPermissionMap);
update
Okay, so I neglected to mention the problem you're actually facing. The form component, smart as it is, will try to call getters and setters when loading/modifying the form data. Since your form contains ->add('companyUserPermissionMaps',..., the form component will call getCompanyUserPermissionMaps on your entity (which will return a currently probably empty collection) and will try to write back to the entity via either setCompanyUserPermissionMaps or add/remove instead of set, if they are present.
Since your field actually behaves as if it holds a collection of Company objects, the setting of those on your User object will obviously fail with the error message you encountered:
Expected value of type "App\Entity\CompanyUserPermissionMap" for association field "App\Entity\User#$companyUserPermissionMaps", got "App\Entity\Company" instead.
which absolutely makes sense. So, this problem can be fixed in different ways. The one way which you apparently already tried was setting mapped to false, but instead of false you used 'false' (notice the quotes), which evaluates to true ... ironically. So to use your approach, you would have to remove the quotes. However, I propose a different approach, which I would much prefer!
end update
My general advice would be to hide stuff you don't want to show. So, in your form builder instead of
->add('companyUserPermissionMaps', EntityType::class, [
'required' => true,
'class' => Company::class,
'label' => 'Compañia',
'multiple' => true,
'expanded' => false,
'choice_label' => 'legalName',
'mapped'=>'false'
])
which obviously already is not a field that handles CompanyUserPermissionMaps but companies instead - so apparently you suspected this is semantically something different, you should go back to the User entity and give it a function getCompanies instead
public function getCompanies() {
return array_map(function ($map) {
return $map->getCompany();
}, $this->getCompanyUserPermissionsMaps());
}
public function addCompany(Company $company) {
foreach($this->companyUserPermissionMaps->toArray() as $map) {
if($map->getCompany() === $company) {
return;
}
}
$new = new CompanyUserPermissionMap();
$new->setCompany($company);
$new->setUser($this);
$this->companyUserPermissionMaps->add($new);
}
public function removeCompany(Company $company) {
foreach($this->companyUserPermissionMaps as $map) {
if($map->getCompany() == $company) {
$this->companyUserPermissionMaps->removeElement($map);
}
}
}
you would then call the field companies (so ->add('companies', ...)) and act on Company entities instead of those pesky maps. (I also don't really like exposing ArrayCollection and other internals to the outside. But hey, that's your decision.)
however, if your Maps are going to hold more values at some point, you actually have to work with the maps in your form, and not just with Company entities.

Expected argument of type "integer", "App\Entity\Entreprise" given

I uses two entitys: stagiaire and entreprise. Each stagiaire have one entreprise. I just need to save the id entreprise in the stagiaire table.
When I create a new stagiaire, choose an enterprise for him and save the form, I have the following error
"Expected argument of type "integer" in the stagiaire controller:
"App\Entity\Entreprise" given
Here is the code of the stagiaire entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\StagiaireRepository")
*/
class Stagiaire
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="civilite", type="string", length=3, nullable=false)
*/
private $civilite = 'Mr';
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=24, nullable=false)
*/
private $nom;
/**
* #var string
*
* #ORM\Column(name="prenom", type="string", length=16, nullable=false)
*/
private $prenom;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Entreprise")
* #ORM\JoinColumn(nullable=false)
*/
private $entreprise;
/**
* #var string
*
* #ORM\Column(name="status", type="string", length=12, nullable=false)
*/
private $status;
public function __construct()
{
//$this->date = new \Datetime();
//$this->entreprise = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getCivilite(): ?string
{
return $this->civilite;
}
public function setCivilite(string $civilite): self
{
$this->civilite = $civilite;
return $this;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
public function getEntreprise(): ?int
{
return $this->entreprise;
}
public function setEntreprise(int $entreprise): self
{
$this->entreprise = $entreprise;
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
Here is the code of the form:
use App\Entity\Stagiaire;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class StagiaireType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choicesCivil = [
'Mr' => '1',
'Mme' => '2'
];
$choicesStatus = [
'Gérant' => '1',
'Salarié' => '2'
];
$builder
->add('civilite', ChoiceType::class, [
'data' => '1', // cochée par défaut
'choices' => $choicesCivil,
'expanded' => true, // => boutons
'label' => 'Civilité',
'multiple' => false
])
->add('nom')
->add('prenom')
->add('entreprise', EntityType::class, array(
'class' => 'App:Entreprise',
'placeholder' => 'Choisir une entreprise',
'choice_label' => 'enseigne',
))
->add('status', ChoiceType::class, [
'data' => '1', // cochée par défaut
'choices' => $choicesStatus,
'expanded' => true, // => boutons
'label' => 'Statut',
'multiple' => false
])
->add('create', SubmitType::class, ['label' => 'Enregistrer', 'attr' => ['class' => 'btn btn-primary']]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Stagiaire::class,
]);
}
}
And here is the controller:
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Entity\Stagiaire;
use App\Entity\Entreprise;
use App\Repository\StagiaireRepository;
use App\Form\StagiaireType;
class StagiaireController extends Controller
{
public function fStagiaire($id,$cat,$action, Request $request)
{
if ($action=='insert') {
$stagiaire = new Stagiaire();
}
elseif($action=='update' || $action=='delete') {
$stagiaire = $this->getDoctrine()
->getManager()
->getRepository('App:Stagiaire')
->find($id)
;
}
else { return;}
$form = $this->get('form.factory')->create(StagiaireType::Class, $stagiaire);
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {...
The error occurs at the line $form->handleRequest($request);
I am searching for a few days but I do not found the solution.
Has anybody an idea to help?
Your function setEntreprise(int $entreprise): self is being given an object (an App\Entity\Entreprise), which is not what it is expecting. While the record in the database will be an int (the primary key id on the Entreprise entity - and in fact, it will be a separate database table that refers back to this record's ID with zero or more Entreprise records), it is made available to your code (from getEntreprise()) as the object (so returning the nullable int there would also fail when you come to read the linked entity out - it should be an empty ArrayCollection()).
Your setter & getter need to deal with the objects - and since it's a 'ManyToOne' relationship, there can more more than one. You will likely also want an addEntreprise() function to be able to add more objects. The ORM will deal with saving the data in them to the database, and linking them back in later when this record is read in.
public function __construct()
{
//$this->date = new \Datetime();
//$this->entreprise = new ArrayCollection();
}
# These lines say that `$this->entreprise` is an array - by default empty, but
# it can be filled up with a number of linked objects.
You need to change your functions to accept an entreprise object instead of an id:
public function getEntreprise(): ?Entreprise
{
return $this->entreprise;
}
public function setEntreprise(Entreprise $entreprise): self
{
$this->entreprise = $entreprise;
return $this;
}
and you have to add use App\Entity\Entreprise; to the Stagiaire class.

symfony object could not be converted to string

i'm newbie in symfony and I'm trying to implement a Data Transformer. I followed the documentation and looked different solutions posted in here. But I haven't found what is wrong with my code.
I'm getting this error:
Catchable Fatal Error: Object of class AppBundle\Entity\Todo could not be converted to string
If someone knows another post with the solution, please tell me know where I could look for it.
Thank you in advance.
So, these are my Entities. Todo class
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Todo
*
* #ORM\Table(name="todo")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TodoRepository")
*/
class Todo
{
/**
* #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=20)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="category", type="string", length=20)
*/
private $category;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=10)
*/
private $description;
/**
* #var string
*
* #ORM\Column(name="priority", type="string", length=10)
*/
private $priority;
/**
*
* #ORM\ManyToOne(
* targetEntity="AppBundle\Entity\User",
* inversedBy="todos"
* )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $creatorId;
//... getters and setters
User class:
<?php
// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* #ORM\OneToMany(
* targetEntity="AppBundle\Entity\Todo",
* mappedBy="creatorId"
* )
*/
private $todos;
}
In the User class, I don't have getter/setters
My TodoType
class TodoType extends AbstractType{
private $manager;
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, array(
'label' => 'Name',
'required' => true
)
)
->add('category', TextType::class, array(
'label' => 'Category'
)
)
->add('priority', EntityType::class, array(
'class' => 'AppBundle:Todo',
'choice_label' => 'priority',
)
)
->add('creatorId', TextType::class, array(
'label' => 'creator Id:',
));
$builder->get('creatorId')
->addModelTransformer(new IssueToNumberTransformer($this->manager));
}
public function getName()
{
return 'todo';
}
}
Transformer
<?php
namespace FOS\UserBundle\Form\DataTransformer;
use AppBundle\Entity\User;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class IssueToNumberTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
/**
* Transforms an object (creatorId) to a string (number).
*
* #param creatorId|null $creatorId
* #return string
*/
public function transform($creatorId)
{
if (null === $creatorId) {
return '';
}
return $creatorId->getId();
}
/**
* Transforms a string (number) to an object (creatorId).
*
* #param string $creatorId
* #return creatorId|null
* #throws TransformationFailedException if object (creatorId) is not found.
*/
public function reverseTransform($creatorId)
{
// no issue number? It's optional, so that's ok
if (!$creatorId) {
return;
}
$creatorId = $this->manager
->getRepository('AppBundle:User')
// query for the issue with this id
->find($creatorId);
if (null === $creatorId) {
throw new TransformationFailedException(sprintf(
'A user with number "%s" does not exist!',
$creatorId
));
}
return $creatorId;
}
}
Controller (function)
public function createAction(Request $request){
$em = $this->getDoctrine()->getManager();
// 1) build the form
$todo = new Todo();
$form = $this->createForm(new TodoType($em), $todo);
// 2) handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// 3) save the Todo!
$em->persist($todo);
$em->flush();
return $this->redirectToRoute('todo_create');
}
return $this->render('todo/create.html.twig', array(
'todo'=>$todo,
'form'=>$form->createView()
));
}
It's at this part:
return $this->render('todo/create.html.twig', array(
'todo'=>$todo,
'form'=>$form->createView()
));
Because you are trying to pass the $todo object to your Twig template. You can't do that. What do you need? If you just need the Todo name and you have created getters/setter, you could instead do like this:
return $this->render('todo/create.html.twig', array(
'todo_name' => $todo->getName(),
'form' => $form->createView()
));
Hopefully that makes sense.
EDIT #2 based on comments.
Also this line is wrong in your controller:
$form = $this->createForm(new TodoType($em), $todo);
Should be like this:
$form = $this->createForm(TodoType::class, $todo);

sonata: Dealing with sonata_type_model (one-to-many)

1- I have an Entity:
EmployeeMedicalService
/**
* #ORM\Entity
* #ORM\Table(name="employee_medical_file")
*/
class EmployeeMedicalService extends BaseEntity
{
//
// Some
// Fields
//
/**
* #Assert\NotBlank
* #ORM\ManyToOne(targetEntity="PersonnelBundle\Entity\Lookup\Lookup")
* #ORM\JoinColumn(name="medical_service_id", referencedColumnName="id")
*/
private $medicalService;
//
// getters
// & setters
//
2- Another Entity:
Lookup
/**
* #ORM\Entity
* #ORM\Table(name="lookup")
* #UniqueEntity(fields="name")
*/
class Lookup extends BaseEntity
{
// const ...
const TYPE_MEDICAL_SERVICE = 'medical_service';
// more constants ...
public function __construct($type)
{
$this->type = $type;
}
//
// Some Fields
//
/**
* #var string
* --stuff--
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="type", type="string", length=50)
* #Assert\NotBlank
*/
private $type;
//getters
// &setters
Now in the
EmployeeMedicalServiceAdmin
protected function configureFormFields(\Sonata\AdminBundle\Form\FormMapper $formMapper)
{
$msquery = $this->getModelManager()
->getEntityManager('PersonnelBundle:Lookup\Lookup')
->createQueryBuilder();
$msquery->select('l')->from('PersonnelBundle:Lookup\Lookup', 'l')->where('l.type = :type')
->orderBy('l.name', 'ASC')
->setParameter('type', 'medical_service');
$formMapper
->add(..)
->add('medicalService', 'sonata_type_model', array(
'label' => 'personnel.employee.medical_service.form.medical_service',
'property' => 'name',
'placeholder' => '',
'required' => false,
'query' => $msquery,
))
->add(..)
;
}
** My Problem: **
I need the form for add new lookup(medical service) from inside the EmployeeMedicalService Admin Form to be preloaded with the field Type value set to 'medical_service' When I attempt to add a new Medical Service from inside the EmployeeMedicalService Admin Form or else a new lookup is added without the value if Type set to NULL
This is the
LookupAdmin
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('name', 'text', array(
'label' => 'personnel.lookup.form.name'
))
->add('type', 'hidden', array(
'label' => 'personnel.lookup.form.type',
))
;
}
If you inspect ajax request for the popup form you will notice extra query parameters, such as pcode. You could check if this parameter exists and equals EmployeeMedicalServiceAdmin admin class code then set lookup type to medical_service.
UPDATE
Add this king of logic in the getNewInstance() method:
public function getNewInstance()
{
$type = isset($_GET['pcode']) ? 'medical_service' : '';
$instance = new \PersonnelBundle\Entity\Employee\EmployeeMedicalService($type);
return $object;
}

Resources