I want to create a pdf file out of some route-dependant data
{http://example.com/products/123/?action=update}
$app->finish(function (Request $request, Response $response) {
// Make a pdf file, only if:
// - the route is under /products/
// - the action is update
// - the subsequent ProductType form isSubmitted() and isValid()
// - the 'submit' button on the ProductType form isClicked()
});
As a normal form submission process I have:
public function update(Application $app, Request $request)
{
$form = $app['form.factory']->create(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if (!$form->get('submit')->isClicked()) {
return $app->redirect('somewhere');
}
$product = $form->getData();
$app['em']->persist($product);
$app['em']->flush();
return $app->redirect('product_page');
}
return $app['twig']->render('products/update.html.twig', array(
'form' => $form->createView(),
));
}
Questions:
Should I duplicate all of the conditionals in finish middleware?
How to access the Product entity in finish middleware?
Consider having multiple resource types like Products, Services, Users, ...
Related
I am trying to use the lock component in symfony 3.4, like it is described on
https://symfony.com/doc/3.4/components/lock.html
I want to prevent multiple data changes from different users.
For example user1 is calling the same company form with data, then user2
How can I tell user2, that editing data is blocked by user1 (incl username) ?
UPDATE:
It is used in backend, where a lot of employees editing data of customers, order etc.
this form is just for editing. that means, if they want to update some data, they click "edit". They should be informed when another employee changes this record before the data is loaded into the form. It sometimes takes some time for the employee to change everything. If the employee receives a message when saving it, they have to go back,reload the data and start all over again.
an example out of my controller:
public function CompanyEdit(Request $request)
{
$error = null;
$company_id = $request->get('id');
// if (!preg_match('/^\d+$/', $company_id)){
// return $this->showError();
// }
$store = new SemaphoreStore();
$factory = new Factory($store);
$lock = $factory->createLock('company-edit-'.$company_id, 30);
if(!$lock->acquire()) {
//send output with username
// this data is locked by user xy
return 0;
}
$company = $this->getDoctrine()->getRepository(Company::class)->find($company_id);
$payment = $this->getDoctrine()->getRepository(Companypay::class)->findOneBy(array('company_id' => $company_id));
$form = $this->createFormBuilder()
->add('company', CompanyFormType::class, array(
'data_class' => 'AppBundle\Entity\Company',
'data' => $company
))
->add('payment', CompanyPayFormType::class, array(
'data_class' => 'AppBundle\Entity\CompanyPay',
'data' => $payment
))
->getForm();
$form->handleRequest($request);
$company = $form->get('company')->getData();
$payment = $form->get('payment')->getData();
if ($form->isSubmitted() && $form->isValid()) {
$event = new FormEvent($form, $request);
if ($payment->getCompanyId() == null) {
$payment->setCompanyId($company->getId());
}
try {
$this->getDoctrine()->getManager()->persist($company);
$this->getDoctrine()->getManager()->persist($payment);
$this->getDoctrine()->getManager()->flush();
$this->container->get('app.logging')->write('Kundendaten geƤndert', $company->getId());
} catch (PDOException $e) {
$error = $e->getMessage();
}
if (null === $response = $event->getResponse()) {
return $this->render('customer/edit.html.twig', [
'form' => $form->createView(),
'company' => $company,
'error' => $error,
'success' => true
]);
}
$lock->release();
return $response;
}
You can't (Locks can't have any metadata), but you probably don't want this in the first place.
In this case, you create a Lock when a user opens the edit page and release it when a user submits the form. But what if the users opens the page and doesn't submit the form? And why can't a user even view the form?
This looks like a XY-problem. I think you're trying to prevent users to overwrite data without knowing. Instead, you can add a timestamp or hash to the form that changes after changing the entity. For example:
<form type="hidden" name="updatedAt" value="{{ company.updatedAt()|date('U') }}" />
And in your form:
<?php
if ($company->getUpdatedAt()->format('U') !== $form->get('updatedAt')->getData()) {
throw new \LogicException('The entity has been changed after you opened the page');
}
Disclaimer: code is not tested and just as an example how this solution can look like.
I'm wondering if I could submit forms inside a collection separately? I have very long form collection with buttons to save each subform (Basically filling and validating the form at once would be difficult). So clicking the button suppose to only submit corresponding subform, but it submits whole collection.
getDoctrine()->getManager();
$user = $this->getUser();
if(!count($user->getApplicants())) {
$app = new Applicant();
$app->setUser($user);
$user->setApplicants($app);
}
if(!count($user->getAddresses())) {
$address = new Address();
$address->setUser($user);
$user->setAddresses($address);
}
if(!count($user->getCompanies())) {
$company = new Company();
$company->setUser($user);
$user->setCompanies($company);
}
if(!count($user->getDirectors())) {
$director = new Director();
$director->setUser($user);
$user->setDirectors($director);
}
$form = $this->createForm('AppBundle\Form\UserType', $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($form->getClickedButton() && 'submitApplicants' === $form-
>getClickedButton()->getName()) {
$applicant = $form->getData()->getApplicants()[0];
$applicant->setUser($user);
$em->persist($applicant);
$em->flush();
return $this->render('admin/index.html.twig', [
'form' => $form->createView()
]);
}
if ($form->getClickedButton() && 'submitAddresses' === $form-
>getClickedButton()->getName()) {
$address = $form->getData()->getAddresses()[0];
$em->persist($address);
$em->flush($address);
return $this->render('admin/index.html.twig', [
'form' => $form->createView()
]);
}
if ($form->getClickedButton() && 'submitCompanies' === $form-
>getClickedButton()->getName()) {
$company = $form->getData()->getCompanies()[0];
$em->persist($company);
$em->flush($company);
return $this->render('admin/index.html.twig', [
'form' => $form->createView()
]);
}
if ($form->getClickedButton() && 'submitDirectors' === $form-
>getClickedButton()->getName()) {
$director = $form->getData()->getDirectors()[0];
$em->persist($director);
$em->flush($director);
return $this->render('admin/index.html.twig', [
'form' => $form->createView()
]);
}
//$em->flush();
}
return $this->render('admin/index.html.twig', [
'form' => $form->createView()
]);
}
}
My opinion is that you need to submit the subform via Javascript. Add some js code to the submit button to:
Perform an ajax POST request to a controller action
Make some checks to the form data
Return back either an error message or some HTML (whatever you want)
Do something with that message
That way you will be able to submit each form separately.
Also change the type of the submit button to plain button. Otherwise you will trigger a form submit for the whole page.
This is my simple code
class LuckyController extends Controller
{
public function taskFormAction(Request $request)
{
$task = new Task();
//$task->setTask('Test task');
//$task->setDueDate(new \DateTime('tomorrow noon'));
$form = $this->createFormBuilder($task)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->add('save', SubmitType::class, array('label' => 'Save'))
->getForm();
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$task = $form->getData();
return $this->redirectToRoute('task_ok', array('task' => '123'));
}
return $this->render('pre.html.twig', array('pre' => print_r($task, true), 'form' => $form->createView()));
}
public function taskOKAction(Task $task)
{
return $this->render('ok.html.twig', array('msg' => 'ok', 'task' => print_r($task, true)));
}
}
and this line
return $this->redirectToRoute('task_ok', array('task' => '123'));
makes redirection to taskOKAction, but it lets me just send parameters by URL (?task=123).
I need to send object $task to taskOKAction to print on screen what user typed in form.
How can I do that? I've already red on stackoverflow before asking that the good solution is to store data from form (e.g. in database or file) and just pass in parameter in URL the ID of object. I think it's quite good solution but it adds me responsibility to check if user didn't change ID in URL to show other object.
What is the best way to do that?
Best regards,
L.
Use the parameter converter and annotate the route properly.
Something like (if you use annotation for routes)
/**
* #Route("/task/ok/{id}")
* #ParamConverter("task", class="VendorBundle:Task")
*/
public function taskOKAction(Task $task)
You can also omit the #ParamConverter part if parameter is only one and if is type hinted (as in your case)
From your form action you can do it without actual 302 redirect:
public function taskFormAction(Request $request)
{
$task = new Task();
//$task->setTask('Test task');
//$task->setDueDate(new \DateTime('tomorrow noon'));
$form = $this->createFormBuilder($task)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->add('save', SubmitType::class, array('label' => 'Save'))
->getForm();
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$task = $form->getData();
return $this-taskOKAction($task)
}
}
This is perfectly legal, and your frontend colleague will thank you for lack of the redirects.
Almost same ISSUES: link, link
UPDATE INFO: - $user = $this->getUser(); set the old image while edit(error form submit). The image replaced with the submitted one value(only value not displaying). WHILE ERROR FORM SUBMIT - I NEED TO DISPLAY THE OLD MEDIA.
NO RELATION WITH SONATA ADMIN.
I have Admin and User role. Both have seperate admin area. User admin area has more complex structure.
I added an Image(avatar) to SonataUser , it works good at admin. Its OneToOne - User and Media.
To edit profile at User Dashboard( Its not SonataAdmin - its i created seperately, Its a simple symfony style).
code:
public function editProfileAction() {
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw $this->createAccessDeniedException('This user does not have access to this section.');
}
// Check user has allready media?
$om = $this->getUser()->getImage();
$oldPath = $om ? $this->getMediaPath($om->getId()) : NULL;
$form = $this->creteForm();
$formHandler = $this->get('sonata.user.profile.form.handler');
$process = $formHandler->process($user);
if ($process) {
// if new file - delete old file
$this->deleteOldMedia($om, $oldPath);
$this->flashMSG(0, 'Profile updated!');
return $this->redirectToRoute('fz_user');
}
$x = ['cmf' => '', 'pTitle' => 'Profile'];
return $this->render(self::TEMPLATE, ['x' => $x, 'form' => $form->createView()]);
}
By the above code, works - with one problem. The reference image of old file is not deleting at server folder. New files are added and entity works fine (displaying at template - fine).
So I tried with my own code,
public function editProfileAction() {
$request = $this->get('request');
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw $this->createAccessDeniedException('This user does not have access to this section.');
}
// Check user has allready media?
$om = $this->getUser()->getImage();
$oldPath = $om ? $this->getMediaPath($om->getId(), 'reference') : NULL;
$oldTN = $om ? $this->getMediaPath($om->getId(), 'admin') : NULL;
$form = $this->createForm(ProfileType::class, $user);
$form->handleRequest($request);
$em = $this->getDoctrine()->getEntityManager();
$data = $form->getData();
if ($form->isSubmitted() && $form->isValid()) {
if (($oldPath != NULL) && ($data->getImage()->getBinaryContent() != NULL)) {
$this->deleteFile($oldPath);
$this->deleteFile($oldTN);
}
$em->persist($user);
$em->flush();
$this->flashMSG(0, 'Profile updated!');
return $this->redirectToRoute('fz_user');
}
// $$user->setImage($om);
$x = ['cmf' => '', 'pTitle' => 'Profile'];
return $this->render(self::TEMPLATE, ['x' => $x, 'form' => $form->createView()]);
}
My own code works - with one problem, If image validation is error - the all image at the template are disappeared. So to check i added $user->setImage(NULL); , the result is, the Null image is shown.(NULL image means at template i do if(null){ display my image }). The backend process - image upadate works good.
For now - I'm satisfied with my code. Here I need to make $user->setImage(xx); to the real image. while form submit with error on media. ONly at error on media.
If no media and error submit - works (displaying image).
UPDATE:
I used $em->refresh($user); from this answer , also it failed to update my image.
WHAT I FOUND ISSUE WITH USER: Its not using 'ApplicationSonataUserBundle:User' for SYMFONY app.user . Thats why, when i give $em->refresh($user); it not modifing username and other details. But it modifing the new details of ApplicationSonataUserBundle:User
Finally to solve I REDIRECTED with flash msg..
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.token_storage')->getToken()->getUser();
$entity = $em->getRepository('ApplicationSonataUserBundle:User')->find($user->getId());
if (!$entity) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$form = $this->createForm(ProfileType::class, $entity);
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$em->flush();
return $this->redirectToRoute('fz_user');
}
$em->refresh($user);
$this->flashMSG(1, '' . $form->getErrors(true, false));
return $this->redirectToRoute('fz_user_profile_edit');
}
I want to update the data in two conditions:
When user enters all the fields in form (Name, email, password)
When user does not enter password (I have to update only name & email).
I have the Following formHandler Method.
public function process(UserInterface $user)
{
$this->form->setData($user);
if ('POST' === $this->request->getMethod()) {
$password = trim($this->request->get('fos_user_profile_form')['password']) ;
// Checked where password is empty
// But when I remove the password field, it doesn't update anything.
if(empty($password))
{
$this->form->remove('password');
}
$this->form->bind($this->request);
if ($this->form->isValid()) {
$this->onSuccess($user);
return true;
}
// Reloads the user to reset its username. This is needed when the
// username or password have been changed to avoid issues with the
// security layer.
$this->userManager->reloadUser($user);
}
An easy solution to your problem is to disable the mapping of the password field and copy its value to your model manually unless it is empty. Sample code:
$form = $this->createFormBuilder()
->add('name', 'text')
->add('email', 'repeated', array('type' => 'email'))
->add('password', 'repeated', array('type' => 'password', 'mapped' => false))
// ...
->getForm();
// Symfony 2.3+
$form->handleRequest($request);
// Symfony < 2.3
if ('POST' === $request->getMethod()) {
$form->bind($request);
}
// all versions
if ($form->isValid()) {
$user = $form->getData();
if (null !== $form->get('password')->getData()) {
$user->setPassword($form->get('password')->getData());
}
// persist $user
}
You can also add this logic to your form type if you prefer to keep your controllers clean:
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormInterface $form) {
$form = $event->getForm();
$user = $form->getData();
if (null !== $form->get('password')->getData()) {
$user->setPassword($form->get('password')->getData());
}
});
Easier way:
/my/Entity/User
public function setPassword($password)
{
if ($password) {
$this->password = $password;
}
}
So, any form using User with password will act as expected :)