Payum Stripe data flow with Symfony - 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.

Related

Many To One relationship not working in 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.

Symfony 4.2.3 - Set ManyToOne Id when user register with email domain

My page
User entity :
<?php
s
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use App\Validator\Constraints as AssertPerso;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #ORM\Table(name="app_user")
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne (targetEntity="Formation")
*/
private $customerName;
public function getId(): ?int
{
return $this->id;
}
/**
* #return CustomerName
*/
public function getCustomerName()
{
return $this->customerName;
}
public function setCustomerName($customerName): self
{
$this->customerName = $customerName;
return $this;
}
}
Formation entity :
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\FormationRepository")
*/
class Formation
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $customerName;
public function getId(): ?int
{
return $this->id;
}
/**
* #return CustomerName
*/
public function getCustomerName()
{
return $this->customerName;
}
public function setCustomerName($customerName): self
{
$this->customerName = $customerName;
return $this;
}
public function __toString()
{
return $this->customerName;
}
}
and my form UserType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('customerName', null, array(
'label' => 'Client ',
'required' => true)
)
->add('submit', SubmitType::class, ['label_format' => "Register", 'attr'=>['class'=>'btn-primary btn-block']]);
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
array($this, 'preSetData')
);
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$user = $event->getData();
// $form->remove('customerName');
$user->setEnabled(true);
}
In my database "formation", I have customer_name "xxx".
I want, when the user register, set the customer_name_id in my table "user" with the domain of the email if this email is like "xxxx" and if the domain of this email does not correspond with the customer_name, display error
Can you help me please ? Thank you
edit with Rebru response :
I succeeded to make the custom validator to check if the domain is an existing domain in "formation"
public function validate($value, Constraint $constraint)
{
$mailParts = explode('#', $value);
$mailParts = explode('.', $mailParts[1]);
$domain = $mailParts[0];
if(!$this->entityManager->getRepository(Formation::class)->findBy(array('customerName' => $domain))){
$this->context->buildViolation($constraint->messageWrongDomain)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Email::INVALID_FORMAT_ERROR)
->addViolation();
return;
}
}
But now I do not know how to set the customer_name_id automatically when the user registers ?
Thank you
Edit 2 :
I just tried that for set the customer_name_id in user table.
In my UserController :
$mailParts = explode('#', $user->getEmail());
$mailParts = explode('.', $mailParts[1]);
$domain = $mailParts[0];
$customerNameId = $entityManager->getRepository(Formation::class)->findBy(array('customerName' => $domain));
$customerNameId = $customerNameId[0]->getId();
I get the following error:
Expected value of type "App\Entity\Formation" for association field "App\Entity\User#$customerName", got "integer" instead.
Ok, then your design is wrong - btw. i would suggest never use of manytomany over strings.
The right path to achieve this is to use custom validators ... like described here: https://symfony.com/doc/current/validation/custom_constraint.html
Short:
table with all customers ( id, name, formation_id)
table with all formations ( id, name, domain )
In the custom validator, you can split the customers email ( explode("#", $email) ) and check this against the domains in the formation table. If it match, the validator should return true otherwise false. If the validator is true, you can save the customer.
With this approach the form will never be valid, until it matches the custom validator.

Check start date and end date to be a period of two weeks in Symfony

Could someone assist me with this. I'm having a trouble with creating a query or how to add changes to createAction to achieve the following. On clicking create it checks if a payroll period is valid, because in the payroll week table it is populated with a one week while the user enters a two week period.
Payroll period: payrollperiodid, start date, enddate and state
Payroll Week: id, startDAte, enddate, numofdays, normal hours.
for eg. user enters startdate: 16-07-2017 enddate: 29-07-2017 in payroll period then in the payroll week table period 1 startdate: 16-07-2017 endDate:22-07-2017 period 2 startdate:23-07-2017 enddate:29-07-2017.
Thus the period would be considered valid else error, and then also on create once the period is valid checks if it exists in the payroll period table else error. But i'm not sure how to add the part that ensures that the user enters a period of 2weeks. >7days <=14days, I wouldn't want to use hard numbers how could i achieve this
public function createAction(Request $request,$startDate, $endDate)
{
$entity = new Payrollperiod();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('comtwclagripayrollBundle:Payrollperiod')->findByPayrollPeriod(array('startDate'=>$startDate,'endDate'=> $endDate));
if ($entity){
$this->addFlash('error','ERROR! Payroll Period exist');
return $this->redirect($this->generateUrl('payrollperiod_create'));
}
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('payrollperiod_show', array('payrollperiodid' => $entity->getpayrollperiodid())));
}
return array(
'entity' => $entity,
'form' => $form->createView(), );
}
public function findByPayrollPeriod($startDate, $endDate)
{
return $this->getEntityManager()
->createQuery(
'SELECT p FROM comtwclagripayrollBundle:PayrollWeek
WHERE startDate = :startDate or endDate = :endDate'
)
->setParameter('startDate', $startDate)
->setParameter('endDate', $endDate)
->getResult();
}
****Updates****
<?php
namespace com\twcl\agripayrollBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
//use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* Payrollperiod
*
* #ORM\Table(name="PayrollPeriod")
* #ORM\Entity
*
*/
class Payrollperiod
{
/**
* #var integer
*
* #ORM\Column(name="payrollperiodid", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $payrollperiodid;
/**
* #var \DateTime
*
* #ORM\Column(name="startDate", type="datetime", nullable=false)
* #Assert\Type("DateTime")
*/
private $startdate;
/**
* #var \DateTime
*
* #ORM\Column(name="endDate", type="datetime", nullable=false)
* #Assert\Type("DateTime")
*
*/
private $enddate;
/**
* #var integer
*
* #ORM\Column(name="State", type="integer", nullable=false)
*
*/
private $state;
public function getPayrollperiodid() {
return $this->payrollperiodid;
}
public function getStartdate() {
return $this->startdate;
}
public function getEnddate() {
return $this->enddate;
}
public function getState() {
return $this->state;
}
public function setPayrollperiodid($payrollperiodid) {
$this->payrollperiodid = $payrollperiodid;
}
public function setStartdate(\DateTime $startdate) {
$this->startdate = $startdate;
}
public function setEnddate(\DateTime $enddate) {
$this->enddate = $enddate;
}
public function setState($state) {
$this->state = $state;
}
/**
* Render a payrollPeriodID as a string.
*
* #return string
*/
public function __toString()
{
return (string) $this->getPayrollperiodid();
}
/**
* #Assert\Callback
*/
public function validatePayrollPeriod(Payrollperiod $Payrollperiod,ExecutionContextInterface $context)
{
$conflicts = $this->getDoctrine()
->getRepository('comtwclagripayrollBundle:Payrollperiod')
->findbyPayrollPeriod($Payrollperiod->getstartDate(), $Payrollperiod->getendDate())
;
if (count($conflicts) > 0) {
$context->buildViolation('Start date and end date exists')
->atPath('startdate')
->addViolation();
}
}
}
public function findbyPayrollPeriod(\DateTime $startDate, \DateTime $endDate)
{
$qb = $this->createQueryBuilder('e');
return $qb->andWhere('e.startDate = :startDate AND e.endDate = :endDate')
->setParameter('startDate', $startDate)
->setParameter('endDate', $endDate)
->getQuery()
->execute()
;
}
But i'm still not getting the error message, am I missing something
I think you can solve the issue, the following way
//create new trait
<?php
namespace yourBundlePath\Form\Type\Utility;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
*
* #package Daim\CoreBundle\Form\Type\Utility
*/
trait ContainerTrait
{
/**
* #var ContainerInterface
*/
private $containerObject;
/**
* #param ContainerInterface $container
* #return ContainerInterface
*/
public function setContainer(ContainerInterface $container)
{
return $this->containerObject = $container;
}
/**
* #return ContainerInterface
*/
public function getContainer()
{
return $this->containerObject;
}
}
//form
use yourBundlePath\Form\Type\Utility\ContainerTrait;
class yourFormClass
{
//call after the class declaration
use ContainerTrait;
$builder->addEventListener(
FormEvents::SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
$em = $this->getContainer()->get('doctrine');
$startDate = $form->get('startDate')->getData();
$endDate = $form->get('endDate')->getData();
$entity = $em->getRepository('comtwclagripayrollBundle:Payrollperiod')->findByPayrollPeriod(array('startDate'=>$startDate,'endDate'=> $endDate));
if ($entity){
$form->get('startDate')->addError(
new FormError(
'ERROR! Payroll Period exist'
)
);
}
}
);
Also you can refer the url: https://symfony.com/doc/current/form/dynamic_form_modification.html
One can solve this in various ways. If this is a common problem (and/ or you prefer a global solution) use the Class Constraint Validator. If you don't mind a 'local' solution look at Callback Constraint.
Both are explained in the documentation pages. Another reference is this SO question.
All that is left is how to calculate the difference between dates for that I'd suggest using PHP's DateTime::diff as something like:
$days = $startDate->diff($endDate)->days;
if ($days <= 7 && $days > 14) {
// build my constraint error message because the period is invalid.
}
Update #1
So let me first say after our comment spam, maybe start somewhere a little lighter. It seems your diving in right in the middle without any foundation on Symfony and/ or even PHP. Symfony has excellent tutorials and examples, but if you can't apply those your going to have a hard time.
The callback validate is only to check the difference between the two dates. An entity in general should not talk to the database but just itself / related entity classes.
class Payrollperiod {
...
/**
* #Assert\Callback
*/
public function validatePayrollPeriod(ExecutionContextInterface $context) {
$days = $this->startdate->diff($this->enddate)->days;
if ($days <= 7 && $days > 14) {
$context->buildViolation('There have to be at least 7 and a maximum of 13 days for your payroll period.')
->atPath('enddate') // this is where the message is bound to, can be either start or end date depending on where you prefer.
->addViolation();
}
}
}
Your findbyPayrollPeriod seems valid, as long as it is in your PayrollperiodRepository class file. And you do want to have a single equals check and not see if ranges overlap etc.
This function could also be handled using doctrine's unique constraints on multiple columns eg (user, startdate) and (user, enddate). This should give you an error when you attempt to add it as it then requires a unique value for the two. Even without the findbyPayrollPeriod function.
In your controller your repository line has multiple problems.
You are using an array for arguments not two arguments as the function has.
You are overwriting your form data entity because you are using the same variable name.
And your $startdate and $enddate appear like magic. They are from the entity, so use the getters.
And as a side note you might not want to redirect on the flash, but just continue as normal (so you don't loose your form data).
All in all you would get something partially like:
...
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$entityExists = $em->getRepository('comtwclagripayrollBundle:Payrollperiod')
->findByPayrollPeriod($entity->getStartdate(), $entity->getEnddate());
// If the entity does not exist we can add it and redirect to the new page.
if (!$entityExists) {
// Add our form entity to the database.
$em->persist($entity);
$em->flush();
// redirectToRoute is a controller helper function and is a tiny bit shorter.
return $this->redirectToRoute('payrollperiod_show', array(
'payrollperiodid' => $entity->getPayrollperiodid()
));
}
// Form is valid but we didn't return anything so far.
// So there is an entity with the same period start or end.
// Add a flash and show the form again.
$this->addFlash('error', 'A payroll period is already present
with the same start or end date.');
}
return ...

How to implement NotifyPropertyChanged method onPropertyChanged?

How to implement NotifyPropertyChanged for retrieving entity changes (in Symfony 2.8 project with doctrine)?
I have found several examples, but they are not full:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html
http://www.boostr.in/26896/is-there-a-built-in-way-to-get-all-of-the-changed-updated-fields-in-a-doctrine-2%23googleads
For me it is not clear if i have to implement the own onPropertyChanged function, how ? I would like to retrive the property changes naming the last and the current properties in the controller and format them as a message for the user.
When i search in the documentation, NotifyPropertyChanged is not described or described minimally:
http://symfony2-document.readthedocs.io/en/latest/search.html?q=NotifyPropertyChanged&check_keywords=yes&area=default
http://api.symfony.com/2.8/search.html?search=NotifyPropertyChanged
http://docs.w3cub.com/symfony~2.8/
http://phpdox.de/demo/Symfony2/interfaces/Doctrine_Common_NotifyPropertyChanged.xhtml
It is written that addPropertyChangedListener() — Adds a listener that wants to be notified about property changes. I do not understand how i should put the custom code in this listener and how to execute this listener in such a way, that i could retrieve the entity changes to the controller action.
// src\MeetingBundle\Entity\EventLisObject.php
<?php
namespace MeetingBundle\Entity;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class EventLisObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
}
//C src\MeetingBundle\Entity\Event.php
<?php
namespace MeetingBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Event
*
* #ORM\Table(name="tevent", indexes={#ORM\Index(columns={"keywords"}, flags={"fulltext"})})
* #ORM\Entity(repositoryClass="MeetingBundle\Repository\EventRepository" )
*/
class Event extends EventLisObject
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
* #Assert\NotBlank()
*/
private $title;
/**
* Set title
*
* #param string $title
*
* #return Event
*/
public function setTitle($title) {
// $this->title = $title;
if( $title != $this->title ) {
$this->_onPropertyChanged("title", $this->title, $title);
$this->title = $title; }
return $this;
}
// src\MeetingBundle\Controller\EventMapControllerACL.php
....
/**
* Event controller.
*
* #Route("/eventmapacl")
*/
class EventMapControllerACL extends Controller
{
/**
* #Route("/edit/{id}", name="event_jsMap_edit")
* #Method("GET|POST")
* #Template("MeetingBundle:Event:ev_jsMap_edit.html.twig")
*/
public function editAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('MeetingBundle:Event')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Event entity.');
}
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
// $deleteForm = $this->createDeleteForm($id);
if ($editForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
// here or before flush or before persist i would like to estimate the changes and generate the message to user informing about them.
return $this->redirect($this->generateUrl('event_jsMap_edit', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
);
}

Sonata User Admin - Custom field dependency

I have extended the SonataAdmin class for FOSUser and added 2 custom fields (choice type from external data source): Company and Sector
I'd like to make Sector dependent on Company, so if the user selects a Company it filters the available Sectors.
I though about using FormEvents for filtering at page load, but I don't even know how to get the Company value of the current form.
Here is a part of my custom SectorType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA
, function(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
// Need to get the company value here if set
});
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->getSectors(),
));
}
public function getSectors()
{
$sects = array();
// Need to pass the selected company value to my getList
// (which gets the list of sector as you can imagine)
if (($tmp_sects = $this->ssrs->getList('Sector'))) {
foreach ($tmp_sects as $sect) {
$label = $sect['id'] ? $sect['label'] : '';
$sects[$sect['id']] = $label;
}
}
return $sects;
}
So the question is:
How to get the selected Company from my custom SectorType ?
After that I'll need to be able to refresh the Sector with Ajax, but that will be another question
I had a similar problem. I needed to create a sale entity that needed to be associated in a many to one relationship with an enterprise entity and a many to many relationship with services entities. Here is the Sale Entity:
The thing is that services where available depending on the companies chosen. For instance services a and b could only be provided to company x. And services b and c could only be provided to company y. So in my admin, depending on the chosen company I had to display the available services. For these I needed to do 2 things:
First create a dynamic form with my sale admin, so that on the server side I could get the right services available for the company specified in my sale record. And second, I had to create a custom form type for my company form element, so that when it was changed by the user on the client side, It would send an ajax request to get the right services for the company chosen.
For my first problem, I did something similar to what you were trying to achieve, but instead of creating an specific custom type for my services element, I added de event listener directly in the admin.
Here is the Sale entity:
/**
*
* #ORM\Table(name="sales")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
*/
class Sale
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
public $id;
/**
* #ORM\ManyToOne(targetEntity="Branch")
* #ORM\JoinColumn(name="branch_id", referencedColumnName="id", nullable = false)
* #Assert\NotBlank(message = "Debe especificar una empresa a la cual asignar el precio de este exámen!")
*/
private $branch;
/** Unidirectional many to many
* #ORM\ManyToMany(targetEntity="Service")
* #ORM\JoinTable(name="sales_services",
* joinColumns={#ORM\JoinColumn(name="sale_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="service_id", referencedColumnName="id")}
* )
* #Assert\Count(min = "1", minMessage = "Debe especificar al menos un servicio a realizar!")
*/
private $services;
public function __construct() {
$this->services = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set branch
*
* #param Astricom\NeurocienciasBundle\Entity\Branch $branch
*/
//default value always have to be null, because when validation constraint is set to notblank,
//if default is not null, before calling the validation constraint an error will pop up explaining
//that no instance of Branch was passed to the $branch argument.
public function setBranch(\Astricom\NeurocienciasBundle\Entity\Branch $branch = null)
{
$this->branch = $branch;
}
/**
* Get branch
*
* #return Astricom\NeurocienciasBundle\Entity\Branch
*/
public function getBranch()
{
return $this->branch;
}
/**
* Add service
*
* #param \Astricom\NeurocienciasBundle\Entity\Service|null $service
*/
public function addServices(\Astricom\NeurocienciasBundle\Entity\Service $service = null)
{
$this->services[] = $service;
}
/**
* Get services
*
* #return Doctrine\Common\Collections\Collection
*/
public function getServices()
{
return $this->services;
}
/**
* Sets the creation date
*
* #param \DateTime|null $createdAt
*/
public function setCreatedAt(\DateTime $createdAt = null)
{
$this->createdAt = $createdAt;
}
/**
* Returns the creation date
*
* #return \DateTime|null
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Sets the last update date
*
* #param \DateTime|null $updatedAt
*/
public function setUpdatedAt(\DateTime $updatedAt = null)
{
$this->updatedAt = $updatedAt;
}
So then in the Admin form builder:
protected function configureFormFields(FormMapper $formMapper) {
$em = $this->container->get('doctrine')->getEntityManager();
$branchQuery = $em->createQueryBuilder();
$branchQuery->add('select', 'b')
->add('from', 'Astricom\NeurocienciasBundle\Entity\Branch b')
->add('orderBy', 'b.name ASC');
$formMapper
->with('Empresa/Sucursal')
->add('branch','shtumi_ajax_entity_type',array('required' => true, 'label'=>'Empresa/Sucursal','error_bubbling' => true, 'empty_value' => 'Seleccione una empresa/sucursal', 'empty_data' => null, 'entity_alias'=>'sale_branch', 'attr'=>array('add_new'=>false), 'model_manager' => $this->getModelManager(), 'class'=>'Astricom\NeurocienciasBundle\Entity\Branch', 'query' => $branchQuery))
->end()
;
$builder = $formMapper->getFormBuilder();
$factory = $builder->getFormFactory();
$sale = $this->getSubject();
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function(DataEvent $event) use ($sale,$factory, $em) {
$form = $event->getForm();
$servicesQuery = $em->createQueryBuilder();
$servicesQuery->add('select','s')
->add('from','Astricom\NeurocienciasBundle\Entity\Service s');
if (!$sale || !$sale->getId()) {
$servicesQuery
->where($servicesQuery->expr()->eq('s.id', ':id'))
->setParameter('id', 0);
}
else {
$servicesQuery
->join('s.branch', 'b')
->where($servicesQuery->expr()->eq('b.id', ':id'))
->setParameter('id', $sale->getBranch()->getId());
}
$form->add($factory->createNamed('services','entity',null,array('required' => true, 'label'=>'Servicios','error_bubbling' => true, 'attr'=>array('show_value_label'=>true),'class'=>'Astricom\NeurocienciasBundle\Entity\Service','multiple'=>true,'expanded'=>true,'query_builder'=>$servicesQuery)));
}
);
}
The trick thing was to pass the forms data. It doesn't work to use evet->getData() in the event listener's function. Instead I passed it through the admin->getSubject() method. Then instead of adding a sonataadmin form type, inside the event listener's function, I had to use a plain symfony form type.
The Ajax part as you mentioned is another question. All the weird things on the branch add method in the form builder is related to a customized field type for this matter. Don't worry about it.

Resources