How to inject parameter in a set method at form submit? - symfony

When form is submitted, object's set method (League#setInformation) is called with corresponding data. All is working correctly. (See code below as an example)
I need to pass additional parameters to setInformation, namely current user id which is stored in session data.
That trick would help keeping session and model separate. Maybe useful in different situations too.
Do you know a way to deal with it?
class LeagueFormType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('name');
$builder->add('information', 'collection', [
'type' => new LeagueInformationFormType(),
]);
}
public function setDefaultOptions(\Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver) {
$resolver->setDefaults([
'data_class' => 'xxx\Models\League',
]);
}
public function getName() {
return 'league';
}
}
class League {
public function getInformation() {
//...
}
public function setInformation($data) {
...
}
}

What I would do is declare form as a service, and inject the data from session. If you can, try to refactor your setInformation() function to two functions for example, so you dont have to provide all information through that one. However I think form events will help you set everything as you like.

If you are using Doctrine2 and the League class is actually a Doctrine2 Entity, I would recommend using a Doctrine2 subscriber/listener.
You can configure the subscriber/listener to do something either just before sending the data to the databse (onFlush), just after telling doctrine about a new entity (persist) or just before updating an existing record (update), whichever is the most appropiate in your case.
Inject the SecurityContext (#security.context in your DIC) into the subscriber/listener to pull out the current user information. (Make sure you check there is a user, because the subscriber wil also be run when nobody is logged in and a League object is saved)
The main advantage of this is that is does not pollute your form or controller. And if for some reason you create a League entity some other way the current user wil also be set.
Some docs:
http://doctrine-orm.readthedocs.org/en/latest/reference/events.html
It's a different story if you are not using Doctrine2 though.

Related

Symfony Forms: how to map only one of the fields of an embedded Form-Type

I am trying to develop a form that will later be used as a base-form for specialized variants.
So it will have inherit_data => true. It is furthermore mapped to a base-data-class.
In this base-form I also use a custom form-type that has some fields. This is where I am currently stuck. I want to have only one of the fields of the custom inner form-type to map back to the actual base-form so the mapping to the base-data-class can be done correctly.
I will try to show this by some simplified classes that hopefully convey what I want:
class BaseFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('unproblematicProperty')
->add('myProblematicProperty', InnerFormType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'inherit_data' => true,
'data_class' => BaseDataClass::class,
]);
}
}
Then some InnerFormType:
class InnerFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('shouldBeMappedToMyProblematicProperty')
->add('shouldntBeMapped1')
->add('shouldntBeMapped2')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
]);
}
}
The goal is that the data-class can be instantiated correctly without me having to make parts of the
BaseFormType unmapped even though they hold actual data for the objects to be instantiated. I.e. I don't want to have part of the object hydration to be done by binding the form-type to the data-class and part of it manually in a controller.
So, how can I achieve a direct "backmapping" of the value from shouldBeMappedToMyProblematicProperty to myProblematicProperty?
If the question needs improvement or further information I will try my best to edit accordingly.
As I found out, this can elegantly be achieved by implementing DataMapperInterface in the InnerFormType.
The contract is to implement two methods:
mapDataToForms($viewData, iterable $forms)
mapFormsToData(iterable $forms, &$viewData)
Remark that if you need to change the representation of your data (in my case for example wrapping strings into UnicodeStrings and vice-versa), that the DataMapper is not the place to do so.
To do this use DataTransformers (implement DataTransformerInterface).
Remark that on get the DataMapper's method mapDataToForms is called
before DataTransformers transform while on form-submit DataTransformer's reverseTransform is called before DataMapper's mapFormsToData.
See also: https://symfony.com/doc/current/form/data_mappers.html
To get a better understanding of the inner ongoings I found enlightening to
dd(...) some of the involved vars at certain points in the flow as the documentation is somewhat lacking in this area in my opinion.

Custom proxy entities for fixture generation

So my question is very specific but I couldn't figure out how to make it even after several months of reflection. The following topic will be about Symfony, Doctrine and generating fixtures on-the-go for the tests.
I want to generate fixtures on the go from a test. The goal is to provide a very specific set of fixtures for each tests using helpers without sacrify the readability. That is the goal, so my idea was to create a tests/Resources/EntityProxy which is a mirror of the src/Entity folder, containing the same amount of classes with the exact same name. Each EntityProxy extends from its related Entity, use a custom trait to fill the properties easily.
You guessed it, I want to only use in tests the EntityProxy and use it directly into the functions to tests them. And there is a major issue with that, as Doctrine doesn't recognize the EntityProxy as an entity even if it extends from a real Entity.
Is there a way to say to Doctrine to persist an EntityProxy as its extended Entity?
__
The following code is an example of what I want as en EntityProxy:
namespace Tests\Resources\EntityProxy;
class User extends App\Entity\User
{
use FixtureGenerationTrait;
public function static makeDefault(): self
{
return static::generate([
'username' => self::getFaker()->username,
'email' => self::getFaker()->email,
...
]);
}
public function static make(array $data = []): self
{
$entity = static::makeDefault();
$entity = static::setValues($entity, $data);
return $entity;
}
}
And can be used in the tests like following: User::make(['name' => 'John Wick']);

What is the best way to create a singleton entity in Symfony 4?

I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.

Symfony2-entityManager inside an Entity

I am using Symfony version 2.7.6. I have created an entity named EmployeeBasicInfo having fields
firstname
lastname
identificationCode etc
I have created a callback function for validating Identification code in EmployeeBasicInfo entity itself which looks like
/**
* #Assert\Callback(groups={"edit_myinfo"})
*/
public function validateIdentificationCode(ExecutionContextInterface $context)
{
if ($this->getEmployeeFirstName() == 'fakename') {
$context->buildViolation('This name sounds totally fake!')
->atPath('employeeFirstName')
->addViolation();
}
}
and this callback function works properly
Actually I want such a callback functionality which checks identidfication code against database. I have added $em = $this->getDoctrine()->getManager(); inside the callback function and the error is like Attempted to call an undefined method named "getDoctrine" of class "XXX\EmployeeBundle\Entity\EmployeeBasicInfo".. Please advise me the effective way
Do not inject the EntityManager in your Entity. One basic concept of the DataMapper-Pattern is, that your entity does not have to know about your data source and its connectors.
I'd suggest to write a custom validation constraint, in which you inject the dependencies you need.
EntityManager, Repository to query, etc. Whatever service suits you.
Have a look at how to create custom constraint validators with dependencies
I would suggest you use a service to do this
class EmployeeUtility($connection)
{
public function __construct($conn) { $this->connection = $v; }
public function validateIdentificationCode($emloyeeId, $validationCode)
{
// Your code here
}
}
In your controller, you inject the service:
$employeeUtility = $this->get('employee.utility');
$employeeUtility->validateIdentificationCode(1,'GF38883dkDdW3373d');
Alternatively, add the code in a repository class.

Set a variable in Symfony2 to be used for all Doctrine calls

Okay, so I'm not even sure how to ask this question (much less search for it). But in my system, I have a variable that forms a relationship for nearly every row. The user does not know it and it is set as a session variable each time a user logs in.
I need this variable to be available to Doctrine. It's not a default or static, so setting it in the class property isn't an option. Having it as a hidden form poses a security risk. I'm honestly at a loss. I've avoided the problem until I can't avoid it no more...
It'd accept a workaround for the time being. I really need to get this project launched as soon as humanly possible.
Any help would be greatly appreciated. Even help explaining what I'm trying to accomplish would be appreciated!
While this doesn't resolve my issue entirely, this particular solution may help someone else in a similar predicament...
In order to inject (I use the term loosely) an object into my form data using a form extension and an event listener.
Extension:
<?php
namespace Acme\DemoBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormEvents;
use Acme\DemoBundle\Form\EventListener\MyListener;
class FormTypeMyExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
return 'form'; // because we're extending the base form, not a specific one
}
public function buildForm(FormBuilder $builder, array $options)
{
$listener = new MyListener($this->security, $this->em);
$builder->addEventListener(FormEvents::SET_DATA, array($listener, 'onSetData'));
}
}
Listener:
<?php
namespace Acme\DemoBundle\Form\EventListener;
use Symfony\Component\Form\Event\FilterDataEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\ORM\EntityManager;
class MyListener
{
public function onSetData(FilterDataEvent $event)
{
// I use form.set_data because it has a method to set data to the form.
$form = $event->getForm();
$data = $event->getData();
// do things to the form or the data.
}
}
(Had some help from this site.)
That allows you to do anything to the form or form data to every form. Which is how I injected the object initially. My problem is the embedded forms apparently don't call setData() (presumably because the first object already has the other objects).
I've worked on this all day, so, if my answer is badly worded, complain and I'll fix it in the morning!

Resources