Symfony extends a form with dependencies from submit data - symfony-forms

I am trying to extends my form, but I dont know how to do it ...
The problem
My parent formType depends of a option. But I want provide that option from the child form. In symfony documentation they explain a method to add dynamic fields that depends on submit data. But if It have a field with DataTransformer?, because in FormInterface I can't add it.
The code
class TransactionApiType extends AbstractApiType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$app = $options['app'];
$builder
->add('gamer', TextType::class)->addModelTransformer(new GamerExternalIdToStringCreateIfNotExistTransformer($em, $app))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Transaction::class,
))
->setAllowedTypes('app', ['AppBundle\Entity\App'])
;
}
}
class TransactionMultiAppConfByAppApiType extends TransactionApiType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('app_id',
EntityType::class,
[
'property_path' => 'app',
'required' => true,
'description' => 'App id',
'class' => App::class,
])
;
// I need pass $options['app'] here to work (App will be submitted),
// How can do? or other possibilities
parent::buildForm($builder, $options);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Transaction::class,
)
;
}
}
Thanks in advance :-)

Add fields with DataTransformers in formEvents are not enabled https://github.com/symfony/symfony/issues/9355
But there is a "hack"
related here Symfony2 form events and model transformers (Summary it needs create a customType...)
My solution was
class TransactionMultiAppConfByAppApiType extends TransactionApiType
{
// ...
parent::buildForm($builder, $options);
$optionsApp = $builder->get('gamer')->getAttributes()['data_collector/passed_options'];
$builder->remove('gamer');
$builder->get('app_id')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($em, $optionsApp) {
/** #var App $app */
$app = $event->getForm()->getData();
$f = $event->getForm()->getParent();
if ($app)
{
$f
->add('gamer', GamerIdWithExternalIdCustomType::class, $optionsApp + ['em' => $em, 'app' => $app])
;
}
});
// ...
}
class GamerIdWithExternalIdCustomType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new GamerExternalIdToStringCreateIfNotExistTransformer($options['em'], $options['app']));
}
public function getParent()
{
return TextType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setRequired([
'em',
'app',
]);
}
}

Related

Laravel FormRequest fetch parameter from url

In my update method, I am using FormRequest to validate the input fields. I also need to pass and additional parameter for unique validation rule.
I am trying to fetch the $subscriber variable from the url.
SubscriberController.php
class SubscriberController extends Controller
{
public function update(UpdateSubscriberRequest $request, $subscriber)
{
...
}
}
UpdateSubscriberRequest.php
class UpdateSubscriberRequest extends FormRequest
{
private $data = [];
private $subscriber = null;
public function __construct()
{
$this->data = request()->all();
$this->subscriber = request()->route('subscriber');
}
public function rules()
{
return [
...
'email' => [
'required',
...
"unique:subscribers,email,{$this->subscriber},token,company_id,"
. session()->get('COMPANY_ID')
],
'mobile' => [
'required',
...
"unique:subscribers,mobile,{$this->subscriber},token,company_id,"
. session()->get('COMPANY_ID')
],
...
];
}
}
Although the code is working fine. But, is this the right way to do this..?

The class 'Login\Entity\User' was not found in the chain configured namespaces \Entity

i'm using zf2 and doctrine that i've configured module.config.php like this
return array(
'doctrine' => array(
'driver' => array(
__NAMESPACE__. '_driver' => array(
'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/' . __NAMESPACE__ . '/Entity')
),
'orm_default' => array(
'drivers' => array(
__NAMESPACE__.'\Entity' => __NAMESPACE__.'_driver'
)
)
)
),
and my LoginController
class LoginController extends AbstractRestfulController {
public function indexAction() {
$em = $this->getServiceLocator()-> get('Doctrine\ORM\EntityManager');
$usr = new User();
$usr->setUsername('yassine');
$usr->setPassword('yassine');
$usr->setEmail('yassine#gmail.com');
$em->persist($usr);
$em->flush(); } }
and my class user /Login/Entity/User
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(name="id")
*/
protected $id;
/**
* #ORM\Column(name="username")
*/
protected $username;
/**
* #ORM\Column(name="password")
*/
protected $password;
/**
* #ORM\Column(name="email")
*/
protected $email;
// Returns ID of the user
public function getId()
{
return $this->id;
}
// Sets ID of the user.
public function setId($id)
{
$this->id = $id;
}
// Returns username.
public function getUsername()
{
return $this->username;
}
// Sets username.
public function setUsername($username)
{
$this->username = $username;
}
// Returns password.
public function getPassword()
{
return $this->password;
}
// Sets Password.
public function setPassword($password)
{
$this->password = $password;
}
// Sets email.
public function setEmail($email)
{
$this->email = $email;
}
// Returns email.
public function getEmail()
{
return $this->email;
}
}
The problem that it shows me this message Mapping Exception :
The class 'Login\Entity\User' was not found in the chain configured namespaces \Entity
It seems __NAMESPACE__ is empty in your module.config.php and that’s why Doctrine sees the following:
'drivers' => [
'\Entity' => '_driver'
]
instead of:
'drivers' => [
'Login\Entity' => 'Login_driver'
]
To fix this issue, you have to declare the namespace used in module.config.php. In other words, put the following:
namespace Login;
at the top of the file, right after PHP opening tag.
Another solution would be to replace all __NAMESPACE__ occurrences with 'Login' string.

DataMapper not called on child FormType with 'inherit_data' => true (Symfony)

Is this always the case? I've searched the web and read the docs but am none the wiser. I did read that DataTransformers can't be applied when inherit_data is true, which also seems a shame. (What could be the reason?)
I have a FormType 'PermissionType' which maps a 'Permission'. Permission has, as do some other entities, a creation/lastModification DateTime. Having read How to Reduce Code Duplication with "inherit_data" I naturally went on my way to implement the newly found good advice and created a TimeTrackedType.
This child form to PermissionType displays two DateTimeType fields and has inherit_data set to true. They are correctly rendered to the browser but they remain empty however I try to enter data into them. I started off by adding a DataMapper but the one of TimeTrackedType is not getting called. The DataMapper of its parent PermissionType however is, it being a child form itself, and that seems the only place where I can change the value of the DateTimeType fields of TimeTrackedType.
I do hope it's me doing something wrong here because it seems wrong having the inputs created in the child form but having to map to them in the parent class. Can anyone elaborate on this? Any pointers are greatly appreciated.
Here are the entities, first User:
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User implements AdvancedUserInterface, \Serializable {
use HasSingleId, TimeTrackedEntityTrait, EntityCreatorTrait;
// ^^^ This trait has two DateTime fields and that's it.
// (...)
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Permission", mappedBy="user")
* #Assert\Valid()
*/
private $permissions;
// (...)
}
Then Permission:
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\PermissionRepository")
*/
class Permission {
use TimeTrackedEntityTrait, EntityCreatorTrait;
/**
* #var User
* #ORM\Id
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="permissions")
*/
private $user;
/**
* #var array
* #ORM\Id
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Role", inversedBy="permissions")
*/
private $role;
// (...getters and setters...)
}
Lastly class Role:
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\RoleRepository")
*/
class Role implements RoleInterface {
use HasSingleId, TimeTrackedEntityTrait, EntityCreatorTrait;
/**
* #var type string
* #ORM\Column(type="string", nullable=false, unique=true);
*/
private $name;
/**
* #var type ArrayCollection
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Permission", mappedBy="role")
* #Assert\Valid()
*/
private $permissions;
}
And now the FormTypes:
class UserType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('username', TextType::class, [ 'attr' => [ 'size' => 10 ] ] )
->add('password', RepeatedType::class, [
'type' => PasswordType::class,
'attr' => ['size' => 10 ],
'first_options' => [ 'label' => 'Password' ],
'second_options' => [ 'label' => 'Confirm' ] ]);
$entity = $builder->getData();
$admin = $entity->hasRole('ROLE_ADMIN');
if($admin) {
$builder->add('id', TextType::class, [ 'attr' => [ 'size' => 4 ] ]);
$builder->add('isEnabled', CheckboxType::class, [ 'required' => false ]);
}
$builder->add('permissions', CollectionType::class, [
'data_class' => 'Doctrine\ORM\PersistentCollection',
'mapped'=>true,
'prototype'=>true,
'allow_add'=>true,
'allow_delete'=>true,
'entry_type' => PermissionType::class]);
$builder->add('email', EmailType::class);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'required' => true,
'mapped' => true,
'data_class' => 'AppBundle\Entity\User'
]);
}
}
...and...
class PermissionType extends AbstractType implements DataMapperInterface {
public function mapDataToForms($permission, $forms) {
$forms = iterator_to_array($forms);
if($permission instanceof Permission && $permission) {
$forms['role']->setData($permission->getRole()->getName());
// These two statements get the job done, but not as was intended.
$forms['created']->setData($permission->getCreated());
$forms['lastModified']->setData($permission->getLastModified());
}
}
public function mapFormsToData($forms, &$permission) {
$forms = iterator_to_array($forms);
if($permission instanceof Permission) {
$permission->setCreated($forms['created']->getData());
$permission->setLastModified($forms['lastModified']->getData());
}
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->setDataMapper($this);
$builder->add('role', TextType::class, [ 'mapped' => true ]);
$builder->add('timing', TimeTrackedEntityType::class, [
'data_class' => 'AppBundle\Entity\Permission',
'inherit_data' => true, 'mapped'=>true ]);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Permission',
'mapped'=>true,
'compound'=>true,
));
}
public function getParent() {
return FormType::class;
}
public function getName() { return 'PermissionType'; }
}
...and finally:
class TimeTrackedEntityType extends AbstractType implements DataMapperInterface {
// This is the method that doesn't get called
public function mapDataToForms($permission, $forms) {
$forms = iterator_to_array($forms);
$forms['created']->setData($permission->getCreated()->format("d/m/Y H:i:s"));
$forms['lastModified']->setData($permission->getLastModified()->format("d/m/Y H:i:s"));
}
public function mapFormsToData($forms, &$data) {
$forms = iterator_to_array($forms);
$data->setCreated($forms['created']->getData());
$data->setLastModified($forms['lastModified']->getData());
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->setDataMapper($this);
$builder->add('created', DateTimeType::class, [
'format' => 'd/M/Y H:i:s',
'input' => 'datetime',
'widget' => 'single_text',
'attr'=>['size'=>14, 'class'=>'right'],
'mapped' => true ]);
$builder->add('lastModified', DateTimeType::class, [
'format' => 'd/M/Y H:i:s',
'input' => 'datetime',
'widget' => 'single_text',
'attr'=>['size'=>14, 'class'=>'right'],
'mapped' => true ]);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'mapped'=>true,
'compound'=>true,
'inherit_data' => true,
));
}
public function getName() { return 'TimeTrackedEntityType'; }
}
The article does not use DataMapper at all.
Using a trait means the properties are part of the PermissionEntity object as normal, so instead of holding these properties like the other fields in the corresponding PermissionType form, they are nested in you sub form type TimeTrackedEntityType.
Then you just need to set inherit_data to true and the right data_class option if you need this sub form else where, and that's what you already do in TimeTrackedEntityType, since the sub form gets its parent form's data, so no need for DataMapper.
If you want to use one, it should only be with the parent form not its child, it is ignored as expected.

ZendFramework2 AbstractTableGateway getSql

I got a ZF2 project with 2 Models PosTable/TopTable which extend AbstractTableGateway.
I want to Paginate results from those Tables so i have a Pagination function in both of them.
this is what the PosTable Model looks like:
...
class PosTable extends AbstractTableGateway {
public function __construct($adapter) {
$this->table = 'pos';
$this->adapter = $adapter;
}
...
public function getPosPaginator($tid) {
$sql = $this->getSql();
$select = $sql->select();
$select->where('tid = '.$tid)->where('deleted = 0')->order('crdate ASC');
$adapter = new \Zend\Paginator\Adapter\DbSelect($select, $sql);
$paginator = new \Zend\Paginator\Paginator($adapter);
return $paginator;
}
...
which works perfectly.
but in my TopTable it looks the same like this:
...
class TopTable extends AbstractTableGateway {
public function __construct($adapter) {
$this->table = 'top';
$this->adapter = $adapter;
}
public function getTopPaginator($fid) {
$sql = $this->getSql();
$select = $sql->select();
$select->where('fid = '.$fid)->where('deleted = 0');
$adapter = new \Zend\Paginator\Adapter\DbSelect($select, $sql);
$paginator = new \Zend\Paginator\Paginator($adapter);
return $paginator;
}
...
my controller looks like this for PosTable:
...
public function posAction(){
...
$pos = $this->getPosTable()->getPosPaginator($tid);
$pos->setCurrentPageNumber($pageid)->setItemCountPerPage(19);
... return $pos etc...
same controller topAction:
...
public function topAction(){
...
$top = $this->getTopTable()->getTopPaginator($fid);
$top->setCurrentPageNumber($pageid)->setItemCountPerPage(20);
...return $top etc..
in that controller i got also these functions:
public function getTopTable(){
return $this->getServiceLocator()->get('Application\Model\TopTable');
}
public function getPosTable(){
return $this->getServiceLocator()->get('Application\Model\PosTable');
}
PosTable Pagination works perfectly, but the TopTable Pagination doesnt work.
i get this error:
Fatal error: Call to a member function select() on a non-object in ....
seems like
$sql = $this->getSql();
doesnt return the object.
how can i solve this problem?
one works one doesnt for no obvious reason.
my module.php looks like this:
namespace Application;
class Module
{
public function getAutoloaderConfig()
{
return array('Zend\Loader\StandardAutoloader' =>
array('namespaces' =>
array(__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,),
),
);
}
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getServiceConfig()
{
return array(
'factories' => array(
'Application\Model\TopTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$table = new \Application\Model\TopTable($dbAdapter);
return $table;
},
'Application\Model\ForTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$table = new \Application\Model\ForTable($dbAdapter);
return $table;
},
'Application\Model\PosTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$table = new \Application\Model\PosTable($dbAdapter);
return $table;
},
),
);
}
}
Ok, after going through TableGateway Code and your extended class, I found the your implementation is not calling initialize which setup the sql object try to call the parent table gateway, below is the modified constructor for your PosTable and TopTable
/* for PosTable */
class PosTable extends TableGateway {
public function __construct($adapter) {
parent::__construct('pos', $adapter);
}
...
/* for TopTable */
class TopTable extends TableGateway {
public function __construct($adapter) {
parent::__construct('top', $adapter);
}
...

Symfony form collection read-only for first entry

How can I set the read-only option only for the first item in the collection when rendering a form?
My simple models:
class Main
{
public $others;
}
class Other
{
public $field1;
public $field2;
}
Simple Form Type for my models:
class MainType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('others', 'collection', array(
'type' => new OtherType(),
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'App\MyBundle\Entity\Main',
));
}
public function getName()
{
return 'maintype';
}
}
class OtherType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field1')
->add('field2')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'App\MyBundle\Entity\Other',
));
}
public function getName()
{
return 'othertype';
}
}
And simple action method my controller
//...
public function indexAction($id)
{
$main = new Main();
$other1 = new Other();
$other1->field1 = 'a';
$other1->field2 = 'b';
$main->others[] = $other;
$other2 = new Other();
$other2->field1 = 'c';
$other2->field1 = 'd';
$main->others[] = $other;
$form = $this->createForm(new MainType(), $main);
//...isValid, persist, flush...
}
//...
I can make a condition when manually render the form, but I want to know if possible at the form code to enter such a restriction.
Currently it is not possible to have the rows of a collection have different options. I invite you to create a feature request on the issue tracker if you feel that this would be a valuable addition.

Resources