Get FormField passed options within a FormEvent - symfony-forms

In a Symfony project, I have a Form EventSubscriber acting on several forms.
It aims to disable each field which is already filled.
In the Subscriber when I use:
$childOptions = $child->getConfig()->getOptions();
I receive all resolved options for a child, I want to get only those passed during the form building. (Because form some FormTypes (i.o. DocumentType) it is not possible to reinject all resolved options, some of them causes troubles).
A FormType example :
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('entity',EntityType::class,array(
'class' => 'AppBundle:User',
'choice_label' => 'username',
))
->addEventSubscriber($this->changesSubscriber); // See next class
}
}
The Subscriber :
class ChangesSubscriber implements EventSubscriberInterface
{
// Disables filled inputs
public function postSetData(FormEvent $event)
{
$form = $event->getForm();
foreach($form->all() as $child)
{
$childName = $child->getName();
$childType = $child->getConfig()->getType()->getName();
// Here I receive all resolved options
// But I only want the options passed above during 'buildForm' ('class','choice_label') :
$childOptions = $child->getConfig()->getOptions();
if(!$child->isEmpty()){
$form->add($childName,$childType,array_merge($childOptions,array('disabled'=>true)));
}
}
}
}
This is one example of many use cases, another example could be :
Alsatian\FormBundle ExtensibleSubscriber
-> A formsuscriber to make AJAX submitted choices acceptacles for Choice/Entity/Document Types.
At this time, as you can see, I choosed to only take a couple of resolved options, but I'm not satisfied with this solution.

Sounds like you need to change your approach.
Maybe make a custom form type, and some of the options to it should be the options to create the original type, similar to how CollectionType works.
Maybe it looks a bit like this:
->add('entity', AjaxType::class,array(
'ajax_type' => EntityType:class,
'ajax_options' => [
'class' => 'AppBundle:User',
'choice_label' => 'username',
]
))
That type can add the event that listens for the data and decides what to do.

Related

Modifying the DTO name appearing in OpenAPI (Swagger) schemas in NestJS

I am facing a problem where my DTO types are named one thing, but I want them to appear with a different name in the OpenAPI doc page.
For example, I have a UserDto class that I use in my controller, but wanted it to appear as simply "User" in the schemas section (and everywhere else this applies). Is that possible? Is there any decorator I can use?
I know I can simply modify the class name, but there is already a different user class used elsewhere.
I have searched everywhere with no avail.
BTW, I am using typescript and nestjs.
Every help will be appreciated, thanks!
Out of the box, Nest.js doesn't yet offer a ready-made solution. There is an open pull request (as mentioned earlier) https://github.com/nestjs/swagger/pull/983, but when it will be merged is unknown.
You can change the DTO name in schemas using one of the following approaches:
Add a static name property to your DTO.
class UserDto {
static name = 'User'; // <- here
#ApiProperty()
firstName: string;
// ...
}
But in strict mode, TypeScript will show an error like:
Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'UserDto'.
Write a decorator with an interface as suggested in the pull request and use it until the desired functionality appears in Nest.js.
The decorator adds the name property with the needed value to the wrapper class for the DTO.
type Constructor<T = object> = new(...args: any[]) => T;
type Wrapper<T = object> = { new(): (T & any), prototype: T };
type DecoratorOptions = { name: string };
type ApiSchemaDecorator = <T extends Constructor>(options: DecoratorOptions) => (constructor: T) => Wrapper<T>;
const ApiSchema: ApiSchemaDecorator = ({ name }) => {
return (constructor) => {
const wrapper = class extends constructor { };
Object.defineProperty(wrapper, 'name', {
value: name,
writable: false,
});
return wrapper;
}
}
Use as suggested in the proposal:
#ApiSchema({ name: 'User' }) // <- here
class UserDto {
#ApiProperty()
firstName: string;
// ...
}
And don't forget that in TypeScript 5 the decorator API will change to something close to the implementation in JavaScript 😉
I solved in my case using #ApiModel
like this
#ApiModel(value="MeuLindoDto")
public class NameOriginalClassResponseDto ...

How to inject Service to have a common instance to several clasess them in zend 3

In Zend 3, I cannot to figure out how should injections work, let me show some simple example.
Let's say, I have
class ToolCollector implements ToolCollectorInterface {
public function __construct(){}
public function registerTool(ToolInterface $tool){
$this->tools[] = $tool;
}
}
and also
class ToolA implements ToolInterface {
public function __construct(ToolCollectorInterface $mainCollector){$mainCollector->registerTool($this);}
}
so what I expect to have
class SomeController extends AbstractActionController {
public function __construct(ToolCollectorInterface $toolCollector){$this->collector=$toolCollector;}
public function indexAction(){
new ToolA; // <- just call Tool and it will put itself in ToolCollector, this is I want
new ToolB; // <- just call Tool and it will put itself in ToolCollector, this is I want
new ToolC; // <- just call Tool and it will put itself in ToolCollector, this is I want
return ViewModel(['toolNameList'=>$this->collector->getRegisteredToolNames();])
}
}
I tried to make it as shown in example, when i call new ToolA it shows me error 500 without any description.
// Next is works
class ToolControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
// Next is not reachable, And I do not know why...
class ToolFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
In module.config.php
return [
...
'service_manager' => [
'aliases' => [
ToolCollectorInterface::class => ToolCollector::class,
],
'factories' => [
Service\ToolCollector::class => InvokableFactory::class,
],
],
'controllers' => [
'factories' => [
Controller\App\SomeController ::class => ToolControllerFactory::class,
]
],
...
];
Is it possible to do it?
How to write factory to make it possilbe?
You must understand something very easy to spot using IoC Inversion of Control. The Container is handling the building process and a reference to the instances you need.
So it is cumbersome and useless, in fact, to make a new inside your indexAction().
You should (and, in fact, you must do it to follow best practices) make any new inside your factory for each dependency your SomeController has.
So you will have many factories. For your Controllers, for your Services, for your Repositories, etc. Each factory has its own logic to build the object you need.
If there is no deps, you can use the default InvokableFactory factory without to write yours. If there is at least one dep, then you must write your own factory with returning your Controller / Service / Repository / etc. class which is built as a ready to use object instance.
The code :
class ToolFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
I do not see any ToolFactory inside your config, only a ToolControllerFactory :
'controllers' => [
'factories' => [
Controller\App\SomeController ::class => ToolControllerFactory::class,
]
],
So it is normal that is sending you a http 500 error, because it does not know the factory your are trying to use to build your Controller.
Zend Framework / Laminas is very tied to configuration. You must follow principles and then you can build your own path to make the things work. But, much important thing, you must be structured enough to not lose yourself in the forest.
Go step by step without the will to change the world when you are starting to walk. Dreams are necessary. But you have to handle a learning curve firstly, as any of us !

SYMFONY FORM ChoiceType, multiple=>true. How to by pass the need to implement Data Transformers

In a SYMFONY FORM (ORM is not use (PDO is used for DB query instead)).
I have a class MyEntityType in which the buildForm function has:
$builder->add('my_attribute',ChoiceType::class,array(
'choices'=>$listForMyAttribute,
'multiple'=>'true',
'attr'=>array('data-native-menu'=>'false'),
'label'=>'Multiple Select on my attribute'
));
My attribute is an array of an entity named MyEntity which has:
/**
* #Assert\NotBlank()
*/
private $myAttribute;
With a getter and a setter for that variable $myAttribute.
When I submit the form in the Controller, it doesn't pass the validation check and logs this error:
Unable to reverse value for property path "myAttribute" : Could not find all matching choices for the given values.
When I start to look for solution around this error message, it leads to something named "How to Use Data Transformers" in SYMFONY Cookbook; And it seems a solution would involve to create new Class and write a lot of code for something that one should be able to by-pass in a much straight forward way.
Does anyone have an idea?
My problem was that my array $listForMyAttribute was defined in the buildForm() function and its definition was relying on some conditional.
The conditional to make the array were met when this one was displayed for the first time.
After pushing the submit button, the buildForm was regenerated in the Controller, this second time, the condition were not met to make the array $listForMyAttribute as it was on the first display. Hence the program was throwing a "contraint not met error" because the value submited for that field could not be find.
Today I face exactly the same problem. Solution is simple as 1-2-3.
1) Create utility dummy class DoNotTransformChoices
<?php
namespace AppBundle\DataTransformer;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
class DoNotTransformChoices implements ChoiceListInterface
{
public function getChoices() { return []; }
public function getValues() { return []; }
public function getPreferredViews() { return []; }
public function getRemainingViews() { return []; }
public function getChoicesForValues(array $values) { return $values; }
public function getValuesForChoices(array $choices) { return $choices; }
public function getIndicesForChoices(array $choices) { return $choices; }
public function getIndicesForValues(array $values) { return $values; }
}
2) Add to your field the following additional option:
...
'choice_list' => new DoNotTransformChoices,
...
3) Congratulations!

how to add form validation from controller in zend framework 2

I tried to add a validation from my controller like below. but it always shows this
if ($request->getPost('ownerType') == "Company") {
$form->getInputFilter()->get('companyName')->getValidatorChain()->addValidator('required');
}
shows error. I confused with below error.
Catchable fatal error: Argument 1 passed to Zend\Validator\ValidatorChain::addValidator() must implement interface Zend\Validator\ValidatorInterface, string given, called in E:\xampp\htdocs\hossbrag\module\WebApp\src\WebApp\Controller\JobController.php on line 177 and defined in E:\xampp\htdocs\hossbrag\vendor\zendframework\zendframework\library\Zend\Validator\ValidatorChain.php on line 100
My controller is here
<?php
namespace WebApp\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use WebApp\Entity\User;
use Zend\View\Model\JsonModel;
use vendor\mpdf\mpdf;
class JobController extends AuthenticatedController
{
public function createAction()
{
$form = new \WebApp\Form\JobpostingForm();
$form->get('companyName')->setValueOptions($company);
$checkAgreement = true;
$request = $this->getRequest();
if ($request->getPost('ownerType') == "Company") {
$form->getInputFilter()->get('companyName')->getValidatorChain()->addValidator('required');
}
}
}
What should to change in my controller to get appropriate solution.
If you encounter such a clear error, simply check out the sources ;)
First one to check would be Zend\Validator\ValidatorInterface. The Error shows you, that a Class implementing this interface is excepted. Looking at the code you'll see, the function wants a Class, not just a string.
But since you're used to ZF a little it becomes clear that you know there's other ways to add stuff. So let's take a look at Zend\InputFilter\InputFilter#add(). You'll see that the first param of the add() function indeed asks for a class implementing ValidatorInterface. But it also accepts some other stuff:
/**
* Add an input to the input filter
*
* #param array|Traversable|InputInterface|InputFilterInterface $input
* #param null|string $name
* #return InputFilter
*/
public function add($input, $name = null)
{
//...
}
It also accepts array, Traversable, InputInterface and InputFilterInterface. So choices are there.
Now, i have never done this myself and i sincerely hope this works (if not i suck!), but assuming you're using the array-syntax, all you have to do is this:
[...]->getValidatorChain()->add(array(
'type' => 'Zend\Validator\NotEmpty'
));
Let me know if this worked out for you ;)

Loading classes dynamically in Dart

So, I looked into mirror and they might be an option, but given their async nature they might be really awkward to use or just not viable in the long run. Since they are currently not supported (just a play-thing) they are not really viable at this time anyway.
Question: Given a series of Strings, eg. [ "Foo", "Bar" ] a base class Application and Widget in library corelib; and a corresponding class for each of the strings FooWidget, BarWidget in library applibrary;, what's currently the most elegant method to get Application to turn the strings into instances of the corresponding classes, that works with dart2js.
Equivalent PHP pseudo-example for clarity,
<?php # example
namespace corelib;
class Widget {
function name() {
return \get_called_class();
}
}
class Application {
static function resolve($name, $library) {
$class = $library.'\\'.$name.'Widget';
return new $class;
}
}
namespace applibrary;
class FooWidget extends \corelib\Widget {
// ...
}
class BarWidget extends \corelib\Widget {
// ...
}
$foowidget = \corelib\Application::resolve('Foo', 'applibrary');
$barwidget = \corelib\Application::resolve('Bar', 'applibrary');
echo "{$foowidget->name()} <br> {$barwidget->name()}";
Output
applibrary\FooWidget
applibrary\BarWidget
If you can validate the list of strings, then the best way for the moment (until mirror support in dart2js becomes better baked), is likely an if statement.
// toy implementation
Widget getWidget(name) {
switch (name) {
case "Foo": return new FooWidget();
case "Bar": return new FooWidget();
default: // handle error
}
}
// elsewhere:
var fooWidget = getWidget("Foo");
var barWidget = getWidget("Bar");
The list of xyzWidget classes will be a finite list (as you can't dynamically link in code at runtime anyway).
Of course, a more elegant implementation is to use mirrors (shown below, for reference, although it doesn't currently fulfil the dar2js criteria)
Future<Widget> getWidget(library, name) {
var completer = new Completer<Widget>();
MirrorSystem ms = currentMirrorSystem();
ClassMirror cm = ms.libraries[library].classes[name];
// instantiate an instance of the class
cm.newInstance(null,[]).then((instance) => completer.complete(instance));
return completer.future;
}
// elsewhere:
getWidget("applibrary","FooWidget").then((Widget widget) {
// do something with widget
});

Resources