Many To One relationship not working in Symfony - symfony

I have set the relationship on the entity's to set many customers to a user entity as a collection and added Multiple to the form field...it's posting ok it's just not updating the user_id in the customer table but it was when using OneToOne relation. Any help would be appreciated.
User entity code
/**
* #var Customer[]
* #ORM\OneToMany(targetEntity="App\Entity\Customer", mappedBy="user", cascade={"all"})
* #ORM\JoinColumn(nullable=true)
*/
private $customer;
public function __construct()
{
$this->staffUsers = new ArrayCollection();
$this->customer = new ArrayCollection();
}
/**
* #param Collection|null $customer
* #return $this
*/
public function setCustomer(?Collection $customer): self
{
$this->customer = $customer;
return $this;
}
Customer entity code
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="customer", cascade={"all"})
*/
private $user;
/**
* #return User|null
*/
public function getUser(): ?User
{
return $this->user;
}
/**
* #param User|null $user
* #return $this
*/
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
Controller code
public function newUser(Request $request, UserPasswordEncoderInterface $encoder) : Response
{
/** #var UserRepository $userRepo */
$userRepo = $this->getDoctrine()->getRepository(User::class);
$customer = new Customer();
// make form
$form = $this->createForm(UserType::class,new User());
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
/** #var User $newUser */
$newUser = $form->getData();
// dump($newUser);
// die();
// hold user roles
$roles = ['ROLE_USER'];
// check if admin role
$adminRole = (bool)$form->get('adminRole')->getData();
if($adminRole){
$roles[]='ROLE_ADMIN';
}
// is a customer selected?
if($newUser->getCustomer() && $newUser->getCustomer()->count() > 0){
$roles[]='ROLE_CUSTOMER';
}
$newUser->setRoles($roles);
// encode pw
$newUser->setPassword(
$encoder->encodePassword($newUser,$newUser->getPassword())
);
// create
$userRepo->insert($newUser);
return $this->redirectToRoute('usersListing');
}
return $this->render('admin/users/user-form.html.twig',[
'form'=>$form->createView()
]);
}
Customer entity type on User form
->add('customer',EntityType::class,[
'required'=>false,
'multiple' => true,
'attr'=>[
'class'=>'selectpicker form-control',
'multiple' =>'multiple',
'data-width' => "100%"
],
'label'=>'Customer(s)',
'placeholder'=>'N/A',
'class'=>Customer::class,
'query_builder'=>function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.lname', 'ASC')
->orderBy('c.fname','ASC');
},
'constraints'=>[
new Callback(function(?Collection $customers, ExecutionContextInterface $context) use($userRepo){
// check if the customer is already linked to a user
if($customers && $customers->count() > 0){
/** #var Customer $customer */
foreach($customers as $customer){
if($customer->getUser()){
$context->addViolation('Customer Is Already Linked To User: ' . $customer->getUser()->getUsername());
return false;
}
}
}
return true;
})
]
])

Rename property customer to customers and function from setCustomer to setCustomers, you should also create an addCustomer method in your User class:
public function addCustomer(Customer $customer)
{
$this->customers[] = $customer;
$customer->setUser($this); // sets the owning side, without this your will end up with user_id equal to null
}
And whenever you want to add a customer you just invoke the addCustomer method.
If you want to use the setCustomers method make sure you set the user in your customer entity.

Related

Symfony always return access denied, #Security, controller, isAuthor

I code a simple app (Symfony 4.1.7) with a user and product system
A user can edit his product, but not another user's
My problem, I go on the edit route, it return access denied, even when it's my product
My ProductController :
/**
* #Route("seller/myproduct/{id}/edit", name="seller_edit_product")
* #param Product $product
* #return Response
* #Security("product.isAuthor(user)")
*/
public function edit(Product $product, Request $request): Response
{
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()){
$this->em->flush();
$this->addFlash('success', 'Modify Successfully');
return $this->redirectToRoute('seller_index_product');
}
return $this->render('seller/product/edit.html.twig', [
'product' => $product,
'form' => $form->createView()
]);
}
Product.php
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="product_id")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
public function getUser(): User
{
return $this->user;
}
public function setUser(User $user): self
{
$this->user = $user;
return $this;
}
/**
* #return bool
*/
public function isAuthor(User $user = null)
{
return $user && $user->getProductId() === $this->getUser();
}
In my isAuhor function
== Access Denied
!== I can access the edition of product that Is not mine
User.php
/**
* #ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="user",orphanRemoval=true)
*/
private $product_id;
public function __construct()
{
$this->product_id = new ArrayCollection();
}
/**
* #return Collection|Product[]
*/
public function getProductId(): Collection
{
return $this->product_id;
}
public function addProductId(Product $productId): self
{
if (!$this->product_id->contains($productId)) {
$this->product_id[] = $productId;
$productId->setUser($this);
}
return $this;
}
}
Thank you
Your isAuthor function will always return false as you are comparing an ArrayCollection to a User
You could add a function in User Class definition that checks if a given user have a given product or no.
So in Product.php :
/**
* #return bool
*/
public function isAuthor(User $user = null)
{
return $user && $user->hasProduct($this);
}
And the hasProduction function could be something like this:
// this goes into User.php
/**
* #return bool
*/
public function hasProduct(Product $product)
{
return $this->product_id->contains($product)
}

Symfony form editAction with fileupload not working because no file

I have create a form with 2 fields, (name and file). I have follow this guide https://symfony.com/doc/current/controller/upload_file.html
I have create my CRUD. My addAction is ok. But my edit action is not ok. When i valid the form for juste change the name and not the file (no file on input file) the form send an error "there is no file". How can I do to make editAction working without new file if no change on file ?
I add my project files
ProductController.php EditAction()
/**
* Displays a form to edit an existing product entity.
*
* #Route("/{id}/edit", name="product_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Product $product)
{
$deleteForm = $this->createDeleteForm($product);
$editForm = $this->createForm('AppBundle\Form\ProductType', $product);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('product_edit', array('id' => $product->getId()));
}
return $this->render('product/edit.html.twig', array(
'product' => $product,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
ProductControlle newAction()
/**
* Creates a new product entity.
*
* #Route("/new", name="product_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm(
'AppBundle\Form\ProductType',
$product,
array(
'validation_groups' => array('add')
)
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('product_show', array('id' => $product->getId()));
}
return $this->render('product/new.html.twig', array(
'product' => $product,
'form' => $form->createView(),
));
}
My product entity
/**
* #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)
* #Assert\Type(type="string")
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\Column(type="string")
*
* #Assert\NotBlank(message="Please, upload the product brochure as a PDF file.", groups={"add"})
* #Assert\File(mimeTypes={ "application/pdf" }, groups={"add"})
*/
private $brochure;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Product
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set brochure
*
* #param string $brochure
*
* #return Product
*/
public function setBrochure($brochure)
{
$this->brochure = $brochure;
return $this;
}
/**
* Get brochure
*
* #return string
*/
public function getBrochure()
{
return $this->brochure;
}
My FileUploder (service)
private $targetDir;
public function __construct($targetDir)
{
$this->targetDir = $targetDir;
}
public function upload(UploadedFile $file)
{
$fileName = md5(uniqid()) . '.' . $file->guessExtension();
$file->move($this->getTargetDir(), $fileName);
return $fileName;
}
public function getTargetDir()
{
return $this->targetDir;
}
And my BrochureUploadListener.php
private $uploader;
private $fileName;
public function __construct(FileUploader $uploader)
{
$this->uploader = $uploader;
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$this->uploadFile($entity);
}
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getEntity();
$this->uploadFile($entity);
}
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof Product) {
return;
}
if ($fileName = $entity->getBrochure()) {
$entity->setBrochure(new File($this->uploader->getTargetDir().'/'.$fileName));
}
}
private function uploadFile($entity)
{
// upload only works for Product entities
if (!$entity instanceof Product) {
return;
}
$file = $entity->getBrochure();
// only upload new files
if ($file instanceof UploadedFile) {
$fileName = $this->uploader->upload($file);
$entity->setBrochure($fileName);
}
}
And my services.yml
AppBundle\Service\FileUploader:
arguments:
$targetDir: '%brochures_directory%'
AppBundle\EventListener\BrochureUploadListener:
tags:
- { name: doctrine.event_listener, event: prePersist }
- { name: doctrine.event_listener, event: preUpdate }
- { name: doctrine.event_listener, event: postLoad }
The problem is that when you use
$form->isValid()
it actually validates
#Assert\NotBlank(message="Please, upload the product brochure as a PDF file.")
#Assert\File(mimeTypes={ "application/pdf" })
but you providing string instead of instance of UploadedFile. What you could do is to create validation group to ensure that this assertions would work only when you create new entity:
#Assert\NotBlank(message="Please, upload the product brochure as a PDF file.", groups={"add"})
#Assert\File(mimeTypes={ "application/pdf" }, groups={"add"})
And then add to your form options inside your addAction following line:
'validation_groups' => array('add')
So, your form instantiation inside addAction should look like like this:
$form = $this->createForm(YourFormType::class, null, array(
'validation_groups' => array('add')
));
I have use vichuploader bundle and it's more simply and ok.

Create records in separate table when user registers new account

Im creating a WebBrowser game with Symfony2. What I want to achieve is:
I have a table with Users. When new user registers in the game, new record is added to table fos_user. When new user is registered I also want to put records in the table that stores users resources in the game with starting quantity.
I have read about event listeners but I'm not sure if they are the best way to resolve my problem.
This is the Entity that holds User, type of material and its quantity
/**
* #var int
*
* #ORM\Column(name="quantity", type="bigint")
*/
private $quantity;
/*
* connection material->MaterialStock<-User
*/
/**
*#ORM\ManyToOne(targetEntity="Material", inversedBy="userMaterial")
*
*/
private $material;
/**
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="userMaterial")
*/
private $user;
function getId() {
return $this->id;
}
function getQuantity() {
return $this->quantity;
}
function getMaterial() {
return $this->material;
}
function getUser() {
return $this->user;
}
function setQuantity($quantity) {
$this->quantity = $quantity;
}
function setMaterial($material) {
$this->material = $material;
}
function setUser($user) {
$this->user = $user;
}
}
User entity looks like this
<?php
// src/AppBundle/Entity/User.php
namespace FactoryBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use FactoryBundle\Entity\Factory;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #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();
$this->productionOrders = new ArrayCollection();
}
/**
* #ORM\OneToOne(targetEntity="Factory", mappedBy="user")
*/
private $factory;
/*
* connecting User->materialStock<-Material
*/
/**
*
* #ORM\OneToMany(targetEntity="MaterialStock", mappedBy="user")
*/
private $userMaterial;
/**
* #ORM\OneToMany(targetEntity="ProductionOrders", mappedBy="user")
*/
private $productionOrders;
/** #ORM\OneToMany(targetEntity="ToyStock", mappedBy="user") */
private $userToyStock;
function getId() {
return $this->id;
}
function getFactory() {
return $this->factory;
}
function getUserMaterial() {
return $this->userMaterial;
}
function getProductionOrders() {
return $this->productionOrders;
}
function getUserToyStock() {
return $this->userToyStock;
}
function setId($id) {
$this->id = $id;
}
function setFactory($factory) {
$this->factory = $factory;
}
function setUserMaterial($userMaterial) {
$this->userMaterial = $userMaterial;
}
function setProductionOrders($productionOrders) {
$this->productionOrders = $productionOrders;
}
function setUserToyStock($userToyStock) {
$this->userToyStock = $userToyStock;
}
}
You can use an event subscriber.
<?php
namespace ...;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FilterUserResponseEvent;
use Steora\Api\UserBundle\Entity\User;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use ...\EntityThatHoldsUserTypeOfMaterialAndQuantity;
/**
* ...
*/
class RegistrationCompletionListener implements EventSubscriberInterface
{
/** #var EntityManager */
private $em;
/**
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => 'onRegistrationCompletionSuccess',
);
}
/**
* #param FilterUserResponseEvent $event
*/
public function onRegistrationCompletionSuccess(FilterUserResponseEvent $event)
{
// you can modify response here, but you can remove this line if there is no need to touch response...
$response = $event->getResponse();
$user = $event->getUser();
$entityThatHoldsUserTypeOfMaterialAndQuantity = new EntityThatHoldsUserTypeOfMaterialAndQuantity();
$entityThatHoldsUserTypeOfMaterialAndQuantity->setUser($user);
$entityThatHoldsUserTypeOfMaterialAndQuantity->setQuantity(...);
...
$this->em->persist($entityThatHoldsUserTypeOfMaterialAndQuantity);
$this->em->flush();
}
}
Register your service in service.yml
services:
...
steora.api.user.registration_confirmation:
class: YourBundle\..\RegistrationCompletionListener
arguments: ['#doctrine.orm.default_entity_manager']
tags:
- { name: kernel.event_subscriber }
So, you are listening for some event, and when that event happens, you do stuff that you need :)
Here you can find more events, maybe some other than FOSUserEvents::REGISTRATION_COMPLETED is more suitable for you: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/FOSUserEvents.php
Here is an example from the official docs: http://symfony.com/doc/current/bundles/FOSUserBundle/controller_events.html
This is workflow:
1) A user is filling a registration form and submits his data.
2) If the form is valid, this is what happens:
// friendsofsymfony/user-bundle/Controller/RegistrationController.php
if ($form->isSubmitted()) {
if ($form->isValid()) {
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS, $event);
$userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
$url = $this->generateUrl('fos_user_registration_confirmed');
$response = new RedirectResponse($url);
}
// This is event you are listening for!!!!
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
}
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_FAILURE, $event);
if (null !== $response = $event->getResponse()) {
return $response;
}
}
3) Your listener reacts on event, and in onRegistrationCompletionSuccess() you do your stuff, and after that everything continues as usual :)
Matko Đipalo
Thank you for your answer. If I understood you correctly the workflow of your approach is:
FOSUser:RegistrationController.php creates an event when registration is completed
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
in your class RegistrationCompletionListener in line:
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => 'onRegistrationCompletionSuccess',
);
}enter code here
Im specifieing to what events I want my app to listen too and on line:
public function onRegistrationCompletionSuccess(FilterUserResponseEvent $event)
{
$response = $event->getResponse();
//... make needed actions
$this->yourDependencies->doSomeStufff()...
}
I can tell me script what to do when that event will accour. In my case get the user object and create for him records in database.

Payum Stripe data flow with Symfony

I am trying to create a checkout that allows a user to create an account for a fee (premium accounts, if you will). A user will create an account (marked as unpaid), the user will pay, and then on a successful payment the account is marked as paid. I can create an account, and I can make a charge. My problem is linking the two things together. I'm not sure how to reference the created account from the successful charge. Here is what I have so far.
Payment.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Payum\Core\Model\ArrayObject;
/**
* #ORM\Table
* #ORM\Entity
*/
class Payment extends ArrayObject
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*
* #var integer $id
*/
protected $id;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
CreateProfileController.php
CreateAction
public function createASquareAction(Request $request, $coupon)
{
$newUser = new User();
$registrationForm = $this->getRegistrationForm($newUser);
$registrationForm->handleRequest($request);
if ($registrationForm->isSubmitted() && $registrationForm->isValid()) {
$newSquare = new Square();
$newSquare->setProduct($registrationForm->get('product')->getData());
$newUser->addSquare($newSquare);
$cost = $this->getTotal($newSquare->getProduct(), $registrationForm->get('coupon')->getData());
$noPayment = $this->isAdmin() || $cost == 0;
$em = $this->getDoctrine()->getManager();
$em->persist($newUser);
$em->flush();
if ($noPayment) {
$newSquare->setVerified(true);
$em->persist($newSquare);
$em->flush();
return $this->redirectToRoute('edit', array(
'id' => $newSquare->getMsid()
));
} else {
$gatewayName = 'stripe_checkout_test';
$storage = $this->get('payum')->getStorage('AppBundle\Entity\Payment');
$payment = $storage->create();
$payment["amount"] = $cost;
$payment["currency"] = 'USD';
$payment["description"] = "Memorysquare";
$storage->update($payment);
$captureToken = $this->get('payum')->getTokenFactory()->createCaptureToken(
$gatewayName,
$payment,
'test_payment_done' // the route to redirect after capture;
);
return $this->redirect($captureToken->getTargetUrl());
}
}
return $this->render('create-a-square/create-a-square.html.twig', [
'registrationForm' => $registrationForm->createView(),
'coupon' => $coupon,
]);
}
Payment Complete Action
public function testPaymentDoneAction(Request $request)
{
$token = $this->get('payum')->getHttpRequestVerifier()->verify($request);
$identity = $token->getDetails();
$model = $this->get('payum')->getStorage($identity->getClass())->find($identity);
$gateway = $this->get('payum')->getGateway($token->getGatewayName());
// or Payum can fetch the model for you while executing a request (Preferred).
$gateway->execute($status = new GetHumanStatus($token));
$details = $status->getFirstModel();
return new JsonResponse(array(
'status' => $status->getValue(),
'details' => iterator_to_array($details),
));
}
Any help to get me on track would be greatly appreciated.
The answer to this was adding my order entity (or any entity you'd like) to the Payment (or PaymentDetails) entity like so (NOTE the cascade persist):
Payment.php
// ... all previous code ... //
/**
* #ORM\OneToOne(targetEntity="Order", cascade={"persist"})
* #ORM\JoinColumn(name="orderid", referencedColumnName="orderid")
*/
private $order;
/**
* Set order
*
* #param \AppBundle\Entity\Order $order
*
* #return Payment
*/
public function setOrder(\AppBundle\Entity\Order $order = null)
{
$this->order = $order;
return $this;
}
/**
* Get order
*
* #return \AppBundle\Entity\Order
*/
public function getOrder()
{
return $this->order;
}
Then in the payment preparation, I add the new order to the $payment object
public function createASquareAction(Request $request, $coupon)
{
// ... previous code ... //
$newOrder = new Order();
// do some setting on my order
$payment->setOrder($newOrder);
$storage->update($payment);
// ... rest of code ... //
}
Maybe this will help someone in the future. I also created an event subscriber to check the order onUpdate, and mark as paid if the stripe payment was successful.

Symfony2 relationship not saved

I have 2 entities that are related with a OneToOne relationship. I embeded the inverse side into the form, and everything works fine, except that the link between the relationship is not stored.
So: The "Begeleider" is saved and the "CompetentieProfiel" is saved, but the column that references the "Begeleider" inside "CompetentieProfiel" table is null.
Ath the moment flush() is called, the "Begeleider" object has the "CompetentieProfiel" object as variable.
Contact:
class Contact {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
Begeleider:
class Begeleider extends Contact {
/**
* #ORM\OneToOne(targetEntity="CompetentieProfiel", mappedBy="begeleider" ,cascade={"persist"})
*/
private $competentieProfiel;
}
CompetentieProfiel:
class CompetentieProfiel {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="Begeleider", inversedBy="competentieProfiel",cascade={"persist"})
*/
protected $begeleider;
}
Form:
class BegeleiderType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('competentieProfiel', new CompetentieProfielType());
}
Controller:
public function createAction(Request $request) {
$entity = new Begeleider();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
// Return the ok status and the begeleider html.
$response = new Response(
json_encode(
array(
'status' => 'ok',
)
)
);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
While you are associating the object from one side you are not from the other. So A is associated with B but B isn't associated with A, if that makes any sense.
From what I know the best way is to add a check in your setter to set the associating object like so.
In Aaaa
public function setBbbb(Bbbb $bbbb)
{
if (null === $bbbb->getAaaa() || $this !== $bbbb->getAaaa()) {
$bbbb->setAaaa($this);
}
$this->bbbb = $bbbb;
return $this;
}
In Bbbb
public function setAaaa(Aaaa $aaaa)
{
if (null === $aaaa->getBbbb() || $this !== $aaaa->getBbbb()) {
$aaaa->setBbbb($this);
}
$this->aaaa = $aaaa;
return $this;
}
This way when either of the sides are set then the other side is automatically set too.

Resources