Unable to pass a specific form in the view - forms

I have created a CRUD with Symfony 3 that allows me to create different missions with a few specificities. I want to to create a function that allows someone with a specific role to change a mission's status just by clicking a button, that would be shown in the view like this
{{form_start(missionInProgress) }}
<input type="submit" value="Submit" />
{{form_end(missionInProgress) }}
Since I'm a real newbie and I can't find concrete example on Google, I tried a lot of things, but none worked so far. I tried to create a public function that would modify the mission's status when someone clicks on the input button
public function that updates the mission's status:
/**
* #Route("/{id}/encours", name="mission_encours")
* #Security has_role('ROLE_RECRUTEUR')
* #Method("POST")
*/
public function enCoursAction(Request $request, Mission $mission){
$form = $this->missionInProgress($mission);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em = $this->getDoctrine()->getManager();
$mission->setStatut("En cours");
$em->persist($mission);
}
}
And I also tried to create a private function like the one that allows a mission to be deleted from anywhere.
**Private function that calls the public function: **
/**
* #param Mission $mission
* #Security has_role('ROLE_RECRUTEUR')
* #return \Symfony\Component\Form\Form The form
*/
private function missionInProgress(Mission $mission){
$this->createFormBuilder()
->setAction($this->generateUrl('mission_encours', array('id' => $mission->getId())))
->setMethod('POST')
->getForm();
}
Following the "createDeleteForm" example, I implemented it in the showAction as follow:
/**
* Finds and displays a Mission entity.
*
* #Route("/{id}", name="mission_show")
* #Method("GET")
*/
public function showAction(Mission $mission)
{
$deleteForm = $this->createDeleteForm($mission);
$enCours = $this->missionInProgress($mission); /* There */
return $this->render('mission/show.html.twig', array(
'mission' => $mission,
'delete_form' => $deleteForm->createView(),
'missionInProgress' => $enCours->createView(), /* And there */
));
}
But when I try to see the result, I get the following error:
Error: Call to a member function createView() on null
Obviously nothing gets inside missionInProgress(), but I can't figure why and how to make this damn thing work. I also don't think that everything I did was necessary, but I thought that if I do this, I might increase my success chances...
Anyone has an idea ?
Thank you in advance

Try to add returnin your missionInProgress() method

Related

Laravel Backpack - Impersonate Operation

Im trying to create an impersonate operation within my user controller, I have been following this guide..
impersonate for backpack
The setupImpersonateDefaults function gets called ok but i get a 404 error, after some testing i figured out the setupImpersonateRoutes is not getting triggered
Any ideas on why?
<?php
namespace App\Http\Controllers\Admin\Operations;
use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;
use Illuminate\Support\Facades\Route;
use Session;
use Alert;
trait ImpersonateOperation
{
/**
* Define which routes are needed for this operation.
*
* #param string $segment Name of the current entity (singular). Used as first URL segment.
* #param string $routeName Prefix of the route name.
* #param string $controller Name of the current CrudController.
*/
protected function setupImpersonateRoutes($segment, $routeName, $controller)
{
Route::get($segment.'/{id}/impersonate', [
'as' => $routeName.'.impersonate',
'uses' => $controller.'#impersonate',
'operation' => 'impersonate',
]);
}
/**
* Add the default settings, buttons, etc that this operation needs.
*/
protected function setupImpersonateDefaults()
{
CRUD::allowAccess('impersonate');
CRUD::operation('impersonate', function () {
CRUD::loadDefaultOperationSettingsFromConfig();
});
CRUD::operation('list', function () {
// CRUD::addButton('top', 'impersonate', 'view', 'crud::buttons.impersonate');
CRUD::addButton('line', 'impersonate', 'view', 'crud::buttons.impersonate');
});
}
/**
* Show the view for performing the operation.
*
* #return Response
*/
public function impersonate()
{
CRUD::hasAccessOrFail('impersonate');
// prepare the fields you need to show
$this->data['crud'] = $this->crud;
$this->data['title'] = CRUD::getTitle() ?? 'Impersonate '.$this->crud->entity_name;
$entry = $this->crud->getCurrentEntry();
backpack_user()->setImpersonating($entry->id);
Alert::success('Impersonating '.$entry->name.' (id '.$entry->id.').')->flash();
// load the view
return redirect('dashboard');
// load the view
//return view('crud::operations.impersonate', $this->data);
}
}
Have tried following the guides and the routes are not getting added.
for anyone else looking at this, you need to call the route from the \routes\backpack\custom.php file, if its not called from this file it wont trigger the setupXXXRoute function
One of the official Backpack team members has created an add-on for impersonating users. You can use his add-on or get inspiration from it:
https://github.com/maurohmartinez/impersonate-users-backpack-laravel

Why doesn't non doctrine-mapped ArrayCollection get initialized?

My Setup is a Symfony 3.4 App with the typical 'ManyToMany'-Relation with additional fields, something like this:
Entity Article
Entity Specialty
Entity ArticleSpecialtyRelation
In a Form for an Article i wanted it to look like as if it were a ManyToMany-Relation rendered as an EntityType with multiple=true and expanded=true, so all entries of Specialty are rendered as checkboxes.
To achieve that i created a non orm-mapped property specialties that is an ArrayCollection, gets initialized in the Constructor and has a Getter, Adder and Remover.
/**
*
* #var ArrayCollection;
*
*/
protected $specialties;
public function __construct()
{
$this->specialties = new ArrayCollection();
}
/**
* #return Collection|Specialty[]
*/
public function getSpecialties()
{
return $this->specialties;
}
/**
* #param Specialty $specialties
*/
public function addSpecialties(Specialty $specialties)
{
$this->specialties->add($specialties);
}
/**
* #param Specialty $specialties
*/
public function removeSpecialties(Specialty $specialties)
{
$this->specialties->removeElement($specialties);
}
This property is used to render the Specialty Entity as checkboxes:
add('specialties', EntityType::class,array(
'class' => Specialty::class,
'expanded'=>true,
'multiple'=>true,
'label'=>'Specialties',
'required' => false,
'mapped'=>true,
));
To populate it with the data from SpecialtyRelation i added a PreSetData Formevent:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$article = $event->getData();
if ($article instanceof Article) {
$form->get('specialties')->setData($article->getUsedSpecialties());
}
});
The used Getter of $artikel just iterates over $article->getArtikelSpecialties and returns a collection of Specialty.
It all works until the submit. Because the formfield is mapped=true, somewhere in handleRequest($form) where the entity is hydrated with the form data, it explodes when the Adder for $specialty is called:
Call to a member function add() on null
Because as i just learned, the Constructor is never called by Doctrine and obviously initializes all ORM-ArrayCollections but not the ArrayCollection for the non-mapped property specialties -
Of course I can check if the ArrayCollection is initialized in the Adder and Remover and initialize it there if it is null, but that just feels a bit hacky in a already at least hacky-felt setup and i am wondering if my setup is completely stupid, especially since i didn't find anybody trying to do that (or getting problems with that) on here or elsewhere.
Is there a better solution to this or should i just check the ArrayCollection in Adder and Remover and live happily ever after?
Also, just curious, is there any other way to initialize the ArrayCollection?
P.S. If there are typos in the names it's because i translated the names into english.
Partial Stacktrace
Symfony\Component\Debug\Exception\FatalThrowableError: Call to a
member function add() on null
at src/Test/Bundle/TestBundle/Entity/Article.php:312 at
Test\Bundle\TestBundle\Entity\Article->addSpecialties(object(Specialty))
(vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php:674)
at
Symfony\Component\PropertyAccess\PropertyAccessor->writeCollection(array(object(Article),
object(Article)), 'specialties', object(ArrayCollection),
'addSpecialties', 'removeSpecialties')
(vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php:622)
at
Symfony\Component\PropertyAccess\PropertyAccessor->writeProperty(array(object(Article),
object(Article)), 'specialties', object(ArrayCollection))
(vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php:216)
at
Symfony\Component\PropertyAccess\PropertyAccessor->setValue(object(Article),
object(PropertyPath), object(ArrayCollection))
(vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php:86)
at
Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper->mapFormsToData(object(RecursiveIteratorIterator),
object(Article))
(vendor/symfony/symfony/src/Symfony/Component/Form/Form.php:636) at Symfony\Component\Form\Form->submit(array(), true)
(vendor/symfony/symfony/src/Symfony/Component/Form/Form.php:580)

handle request of incomplete missing fields symfony form

I have created a small Symfony (Sf3.2 + php7) web with a Task Entity. I have my controller where I have a new action to create a new task. Everything is fine. Now, some of the form fields are not necessary to be filled (created date, for example). So I have removed from my taskType:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add->('name')->add('description');
}
Now my form looks exactly as I want. The surprise is that when I submit it my controller pass it to doctrine,and every missing field is write as a NULL. NO!, I don't want that, what I wanted is my default mysql value.
Ok, I read the doc. There seems to be two ways of handling data:
$form->handleRequest($request);
$form->submit($request->request->get($form->getName()));
I've found in the API, (not in the doc) that submit have a second parameter, a boolean:
API says:
bool $clearMissing Whether to set fields to NULL when they are missing in the submitted data.
Great! this is exactly what I need. Lets see it:
public function newAction(Request $request) {
$task = new Task();
$form = $this->createForm('myBundle\Form\TaskType', $task);
$form->submit($request->request->get($form->getName()), false);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
$response = new Response();
$response->setContent(json_encode(array(
'data' => 'success',
)));
$response->headers->set('Content-Type', 'application/json');
return $response;
return $this->redirectToRoute('task_success');
}
return $this->render('task/new.html.twig', array(
'task' => $task,
'form' => $form->createView(),
));
}
Well, after tried everything I always get NULL in all missing fields. What am I doing wrong.
Thank you, Caterina.
I'm afraid there must be something more. And I was thinking that perhaps is not Doctrine the solution. The Symfony log shows me the insert and it's setting a Null in every empty field, on empty fields and missing fields. In fields like status i could set a default value, at doctrine level, but if I want to set created_at field, I suppose that must be Mysql the responsible is setting current timeStamp.
Anyway this is my ORM property status from Task Entity.
/**
* #var \scrumBundle\Entity\Taskstatus
*
* #ORM\ManyToOne(targetEntity="scrumBundle\Entity\Taskstatus")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="status", referencedColumnName="id")
* })
* #ORM\Column(name="status", type="integer", nullable=false,options={"default":1})
*/
private $status;
And this is the property id from TaskStatus:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false, options={"default":1})
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
As you see I tried to follow your advise of setting the option default value. I've tried to create to set the form with handleRequest and submit with same result
$task = new Task();
$form = $this->createForm('scrumBundle\Form\TaskType', $task);
//$form->handleRequest($request);
$form->submit($request->request->get($form->getName()), false);
if ($form->isSubmitted() && $form->isValid()) {
I even tried to debug step by step submit function, a real Hell, because submit is a recursive function to much complex for me.
Ok , again thank for you time.
Regards, Cate
PS. Sorry about my poor english.;^)
Short answer - when you persist a PHP object through Doctrine, you must have absolutely every value set that you want. If your PHP object has null fields, Doctrine will manually set them as null in your entity. Doctrine doesn't assume that just because a value is null in your PHP object, that you don't want it included on your INSERT statement. This is because null is a perfectly valid SQL value.
So, whatever your PHP object is at the time of insert is exactly what is going to be inserted into your database, null values and all.
When you are editing an entry, Doctrine will only update the fields that are different, so this isn't a concern. Your concern is when persisting entities.
The easiest solution is to copy your MySQL default value into your PHP entity. Like one of these many ways:
// set default value on the variable itself
public $someVar = 100;
// set default values in the constructor (so when creating a new entry)
public function __construct()
{
$this->createdAt = new \DateTime();
$this->isActive = true;
}
You can also use Lifecycle Callbacks to set when inserting a new entry:
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Task
{
// ...
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
$this->createdAt = new \DateTime();
}
}

How to dynamically control the validation of a form?

I've got some issues with Symfony's form validation handling. I'd like to validate a form bound to an entity based on its data. There are quite a bunch of information how to dynamically modify the form fields using FormEvents. What I'm missing on this topic is how to control/modify the validation.
My simplified use case is:
A user can add an event to a calendar.
The validation checks if there's already an event.
If there's a collision, the validation will throw an error.
The user should now be able to ignore this error/warning.
The validation is implemented as a Validator with Constraint::CLASS_CONSTRAINT as the target (as it's taking some more stuff into account).
I tried to:
Hack around the validation groups, but couldn't find access to the entity wide validators.
Hack around the FormEvents and add an extra field like "Ignore date warning".
Hack around the submit button to change it to something like "Force submit".
... but never found a working solution. Even simpler hacks with a single property based validator didn't work out. :(
Is there a Symfony way to dynamically control the validation?
Edit: My code looks like this:
use Doctrine\ORM\Mapping as ORM;
use Acme\Bundle\Validator\Constraints as AcmeAssert;
/**
* Appointment
*
* #ORM\Entity
* #AcmeAssert\DateIsValid
*/
class Appointment
{
/**
* #ORM\Column(name="title", type="string", length=255)
*
* #var string
*/
protected $title;
/**
* #ORM\Column(name="date", type="date")
*
* #var \DateTime
*/
protected $date;
}
The validator used as a service:
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the date of an appointment.
*/
class DateIsValidValidator extends ConstraintValidator
{
/**
* {#inheritdoc}
*/
public function validate($appointment, Constraint $constraint)
{
if (null === $date = $appointment->getDate()) {
return;
}
/* Do some magic to validate date */
if (!$valid) {
$this->context->addViolationAt('date', $constraint->message);
}
}
}
The corresponding Constraint class is set to target the entity class.
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class DateIsValid extends Constraint
{
public $message = 'The date is not valid!';
/**
* {#inheritdoc}
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
/**
* {#inheritdoc}
*/
public function validatedBy()
{
return 'acme.validator.appointment.date';
}
}
Edit 2: Try with FormEvents... I also tried all the different events.
$form = $formFactory->createBuilder()
->add('title', 'text')
->add('date', 'date')
->addEventListener(FormEvents::WHICHONE?, function(FormEvent $event) {
$form = $event->getForm();
// WHAT TO DO HERE?
$form->getErrors(); // Is always empty as all events run before validation?
// I need something like
if (!$dateIsValid) {
$form->setValidationGroup('ignoreWarning');
}
});
Edit 3: Constraint are correctly declared. That's not the issue:
services:
validator.acme.date:
class: AcmeBundle\Validator\Constraints\DateValidator
arguments: ["#acme.other_service"]
tags:
- { name: validator.constraint_validator, alias: acme.validator.appointment.date }
Validation is done on the entity, all Forms does is execute the Object's validations.
You can choose groups based on submitted data
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$data = $form->getData();
if (Entity\Client::TYPE_PERSON == $data->getType()) {
return array('person');
} else {
return array('company');
}
},
));
}
I have had issues when using this approach on embedded forms && cascade-validation
Edit: using flash to determine if validation must take place.
// service definition
<service id="app.form.type.callendar" class="%app.form.type.callendar.class%">
<argument type="service" id="session" />
<tag name="form.type" alias="my_callendar" />
</service>
// some controller
public function somAvtion()
{
$form = $this->get('app.form.type.callendar');
...
}
// In the form
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$session = $form->getSession();
if ($session->getFlashBag()->get('callendar_warning', false)) {
return array(false);
} else {
return array('Validate_callendar');
}
},
));
}
How does your user interact with the application to tell it to ignore the warning? Is there some kind of additional button?
In that case you could simply check the button used for submitting the form or add some kind of hidden field (ignore_validation) etc.
Wherever you end up getting that user input from (flash and dependency injection, based on submitted data etc.), I would then use validation groups and a closure to determine what to validate (just like juanmf explained in his answer).
RE your second approach (Form Events), you can add a priority to event listeners: As you can see in Symfony's Form Validation Event Listener, they use FormEvents::POST_SUBMIT for starting the validation process. So if you just add an event listener, it gets called before the validation listener and so no validation has happened yet.
If you add a negative priority to your listener, you should be able to also access the form validation errors:
$builder->addEventListener(FormEvents::POST_SUBMIT, function(){...}, -900);
Old question but...
I would first add a field (acceptCollision) in the form as suggested by you and other answers above.
Then you validator can do something like:
public function validate($appointment, Constraint $constraint)
{
if (null === $date = $appointment->getDate()) {
return;
}
if ($appointment->getAcceptCollision()) {
$valid = true;
} elseif (
// Check Unicity of the date (no collision)
) {
$valid = true;
} else {
$valid = false;
}
if (!$valid) {
$this->context->addViolationAt('date', $constraint->message);
}
}
I think you run into a problem because you are using the wrong concept. The decision which validation should be running belongs to the controller, not the validator.
So I would simply check in the controller which submit button is pressed (or weither there is a checkbox checked) and switch validation groups. However the form should be visually different, so I would probably create 2 forms for both states (both extend a base one or one form type that use options).

An error occurred while trying to call Controller->createAction()

I am trying to create something with extbase, but the error-message I get is not very helpful. I took the blog_example extension as a guide. A (maybe) important difference is: I don't have a database table because I want to write a custom domain repository that connects to an external servive through REST.
The actual error message (displayed above the plugin, not as an exception message):
An error occurred while trying to call Tx_MyExt_Controller_SubscriptionController->createAction()
Classes/Controller/SubscriptionController:
Stripped down to the important parts.
class Tx_MyExt_Controller_SubscriptionController extends Tx_Extbase_MVC_Controller_ActionController
{
/**
* #var Tx_MyExt_Domain_Repository_SubscriberRepository
*/
protected $subscriberRepository;
/**
* #return void
*/
public function initializeAction()
{
$this->subscriberRepository = t3lib_div::makeInstance('Tx_MyExt_Domain_Repository_SubscriberRepository');
}
/**
* #param Tx_MyExt_Domain_Model_Subscriber $subscriber
* #dontvalidate $subscriber
* #return string The rendered view
*/
public function newAction(Tx_MyExt_Domain_Model_Subscriber $subscriber = null)
{
$this->view->assign('subscriber', $subscriber);
}
/**
* #param Tx_MyExt_Domain_Model_Subscriber $subscriber
* #return string The rendered view
*/
public function createAction(Tx_MyExt_Domain_Model_Subscriber $subscriber)
{ }
}
Classes/Domain/Model/Subscriber
class Tx_MyExt_Domain_Model_Subscriber extends Tx_Extbase_DomainObject_AbstractEntity
{
/**
* #var string
* #dontvalidate
*/
protected $email = '';
/**
* #param string $email
* #return void
*/
public function setEmail($email)
{
$this->email = $email;
}
/**
* #return string
*/
public function getEmail()
{
return $this->email;
}
}
Resources/Private/Templates/Subscription/new
<f:form action="create" controller="Subscription" objectName="Subscriber" object="{subscriber}" method="post">
<f:form.textfield property="email"></f:form.textfield>
<f:form.submit value="submit"></f:form.submit>
</f:form>
Facts
Adding $subscriber = null removes the message. But $subscriber is null then
A var_dump($this->request->getArguments()); displays the form's fields
There is an index action, and it is also the first action defined in ext_localconf.php
The hints and solutions I found aren't working for me, so I hope someone can guide me into the right direction.
I've got the same bug.
If you pass an Model as argument to an method, it will also validate the model fields.
I've had this annotation on my model property:
/**
*
* #var \string
* #validate NotEmpty
*/
It validates the "#validate" annotation.
The field in the database was empty so i got the error message
An error occurred while trying to call ...
It would be good if there was a better error message.
You need to customize the validation annotation or verify that the property is not empty in the database
Hope it helps somebody
In addtion: check any Validations in your Model and your TCA. If a field is marked as #validate NotEmpty in your Model and is not marked appropriately in the TCA, a record can be saved ignoring the #validate settings in the Model. This can happen if you change the Model and/or TCA after creating records.
An example:
Field 'textfield' is set to not validate, both in the TCA and the Model. You create a new record and save it without filling in the field 'textfield' (you can, it is not set to validate). You then change the Model setting 'textfield' to #validate NotEmpty and then try to show the record on the FE, you will get the error.
The solution for that example:
Simply remove the validation in your Model OR check validations in the TCA and Model so that they work together.
--
A German blog post covers this solution: http://www.constantinmedia.com/2014/04/typo3-extbase-an-error-occurred-while-trying-to-call-anyaction/
just override the template method getErrorFlashMessage in yout controller to provide a custom error message...
/**
* A template method for displaying custom error flash messages, or to
* display no flash message at all on errors. Override this to customize
* the flash message in your action controller.
*
* #return string|boolean The flash message or FALSE if no flash message should be set
* #api
*/
protected function getErrorFlashMessage() {
return 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '()';
}
classic case of "start over from scratch and it works, and if you compare it you have the same code, though".
I updated the code in the question, maybe it helps someone.