getdata on File input to return only filename, not array of details - zend-framework2

I have a form that has a fieldset with a file upload field in it. When I do a var_dump on $form->getData() I am being shown an array of data for the file field:
array (size=13)
'logo' =>
array (size=5)
'name' => string 'my-image.gif' (length=12)
'type' => string 'image/gif' (length=9)
'tmp_name' => string 'C:\xampp\htdocs\images\my-image.gif' (length=35)
'error' => int 0
'size' => int 391
//... other fields here
How do get the element to return only the name when I call getData?
e.g.
array (size=13)
'logo' => string 'my-image.gif' (length=12)
//... other fields here
I am using the form for other things and have already overridden getData so would like to keep the answer located in the fieldset.

You can override the getData() method in your form.
public function getData()
{
$data = parent::getData();
$logo = $data['logo'];
$data['logo'] = $logo['name'];
return $data;
}
Add all necessary precautions to ensure the existence of the keys in the arrays.
Supplements for a fieldset
Using a fileset, you can use a Filter to change the return file structure :
namespace your\namespace;
use Zend\Filter;
class FilterFileName extends Filter\AbstractFilter
{
public function filter($value)
{
if (! is_scalar($value) && ! is_array($value)) {
return $value;
}
if (is_array($value)) {
if (! isset($value['name'])) {
return $value;
}
$return = $value['name'];
} else {
$return = $value;
}
return $return;
}
}
Your fieldset class must implement InputFilterProviderInterface
use your\namespace\FilterFileName;
class YourFieldset extends ZendFiedset implements InputFilterProviderInterface
{
public function __construct()
{
// your code ... like :
parent::__construct('logo');
$file_element = new Element\File('my-element-file');
$file_element->setLabel('Chooze')
->setAttribute('id', 'my-element-file')
->setOption('error_attributes', [
'class' => 'form-error'
]);
$this->add($file_element);
}
public function getInputFilterSpecification()
{
return [
'element-file' => [
'name' => 'my-element-file',
'filters' => [
['name' => FilterFileName::class]
]
]
];
}
}
You can chain multiple filters, eg to rename the file before.

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..?

How to include EntityManager in ZendFramework 2 AbstractValidator

I have a custom validator, extending Zend AbstractValidator. The thing is, i want to include Doctrine EntityManager, but i keep failing! I tried to make a Factory for my Validator, but it doesn't seem to work. Help!! What am I doing wrong?
Validator:
$this->objectRepository stays empty, while i expect content.
namespace Rentals\Validator;
use Rentals\Response;
use Zend\Validator\AbstractValidator;
use Zend\Stdlib\ArrayUtils;
class ExistentialQuantification extends AbstractValidator
{
const NO_ENTITY_ID = 'noEntityId';
const ENTITY_NOT_FOUND = 'entityNotFound';
const INVALID_ID = 'invalidId';
protected $messageTemplates = [
self::NO_ENTITY_ID => 'The input does not contain an entity id.',
self::ENTITY_NOT_FOUND => 'The entity could not be found.',
self::INVALID_ID => 'The input does not contain an entity id.',
];
protected $objectRepository;
public function __construct(array $options)
{
$this->objectRepository = $options['object_repository'];
parent::__construct($options);
}
public function isValid($value)
{
if ($value === null) {
return true;
}
if (! isset($value->id)) {
$this->error(self::NO_ENTITY_ID);
return false;
}
$entityClass = $this->getOption('entity_class');
$controller = new Controller();
$entity = (new FactoryInterface)(EntityManager::class)->find($entityClass, $entity->id);
if (! $entity instanceof $entityClass) {
$this->error(self::ENTITY_NOT_FOUND);
return false;
}
if (! $entity->getId()) {
$this->error(self::NO_ENTITY_ID);
return false;
}
return true;
}
}
Factory:
namespace Rentals\Validator;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\MutableCreationOptionsInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Stdlib\ArrayUtils;
class ExistentialQuantificationFactory implements FactoryInterface, MutableCreationOptionsInterface
{
protected $options = [];
public function setCreationOptions(array $options)
{
$this->options = $options;
}
public function createService(ServiceLocatorInterface $serviceLocator)
{
if (! isset($this->options['object_manager'])) {
$this->options['object_manager'] = 'doctrine.entitymanager.orm_default';
}
$objectManager = $serviceLocator->get($this->options['object_manager']);
$objectRepository = $objectManager->getRepository($this->options['entity_class']);
return new ExistentialQuantification(ArrayUtils::merge(
$this->options, [
'objectManager' => $objectManager,
'objectRepository' => $objectRepository
]
));
}
}
Module config:
<?php
return [
'service_manager' => [
'factories' => [
'Rentals\\Validator\\ExistentialQuantification' => 'Rentals\\Validator\\ExistentialQuantificationFactory'
]
]
];
?>
What if you change your config entry like the following example?
return [
'validators' => [
'factories' => [
ExistentialQuantification::class => ExistentialQuantificationFactory::class,
],
],
];
This change will result in further changes for your factory, because the service locator for the entity manager differs from the one you injected.
namespace Application\Validator\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\MutableCreationOptionsInterface;
use Zend\ServiceManager\MutableCreationOptionsTrait;
use Zend\ServiceManager\ServiceLocatorInterface;
class ExistentialQuantificationFactory implements FactoryInterface, MutableCreationOptionsInterface
{
use MutableCreatinOptionsTrait;
public function createService(ServiceLocatorInterface $serviceLocator)
{
$parentLocator = $serviceLocator->getServiceLocator();
if (! isset($this->creationOptions['object_manager'])) {
$this->creationOptions['object_manager'] = 'doctrine.entitymanager.orm_default';
}
$objectManager = $parentLocator->get($this->creationOptions['object_manager']);
$objectRepository = $objectManager->getRepository($this->creationOptions['entity_class']);
return new ExistentialQuantification(ArrayUtils::merge(
$this->options, [
'objectManager' => $objectManager,
'objectRepository' => $objectRepository
]
));
}
}
What I 've done here? First I implemented the MutableCreationOptionsTrait class. This trait implements the needed functions for working with creation options. But this is just a little hint for avoiding unnecessary work.
Because of setting the validator class as validator in the config, we have to use the parent service locator for getting the entity manager. The inherited service locator just provides access to validators.
Now you can try to access your validator in your controller like in the following examaple.
$validator = $this->getServiceLocator()
->get('ValidatorManager')
->get(ExistentialQuantification::class, [
'entity_class' => YourEntityClass::class,
]);
\Zend\Debug\Debug::dump($validator, __METHOD__);
The validator manager should return your validator so that you can test it.

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.

Can't get bind() to work

I want my form fields to contain the previous data contained in database when the form page opens. I went through lots of queries here and came to know using populate() or bind() method is the way to do it. But when I try to use it, I get an undefined method error.
Is there any other way to do it?
I am unable to use bind() as well. I am getting a fresh form with default values after I submit.
Sorry if this is a stupid question. Its been only 4-5 days since I started learning Zend framework. Also, most of the methods I get online are for older frameworks. I am using Zend Framework2.
This is Controller Code
<?php
class ChatController extends AbstractActionController
{
protected $chatTable;
public function indexAction()
{
$form = new ChatForm();
$model= new Chat();
$form->bind($model);
$form->get('submit')->setValue('Save');
$request = $this->getRequest();
if ($request->isPost()) {
$gen_set = new Chat();
$form->setInputFilter($gen_set->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$gen_set->exchangeArray($form->getData());
$this->getChatTable()->saveChat($gen_set);
// Redirect to list of albums
return $this->redirect()->toRoute('chat');
}
}
return array('form' => $form);
}
public function getChatTable()
{
if (!$this->chatTable) {
$sm = $this->getServiceLocator();
$this->chatTable = $sm->get('Chat\Model\ChatTable');
}
return $this->chatTable;
}
}
My Entity Class, Here api_key and anon_prefix are rows of the column 'settings' and there is one more column with value.
<?php
class Chat implements InputFilterAwareInterface
{
protected $inputFilter;
public function exchangeArray($data)
{
$this->api_key=(isset($data['api_key'])) ? $data['api_key'] : null;
$this->anon_prefix = (isset($data['anon_prefix'])) ? $data['anon_prefix'] : null;
}
// Add content to these methods:
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$inputFilter->add(array(
'name' => 'iflychat_external_api_key',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
));
$inputFilter->add(array(
'name' => 'iflychat_show_admin_list',
'required' => true,
'validators' => array(
array(
'name' => 'InArray',
'options' => array(
'haystack' => array(1,2),
),
),
),
));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
public function getArrayCopy()
{
return get_object_vars($this);
}
}
This is used to enter values into db
<?php
class ChatTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function saveChat(Chat $gen_set)
{
$data = array(
'value' => $gen_set->api_key,
);
$id='iflychat_external_api_key';
$this->tableGateway->update($data,array('settings' => $id));
$data = array(
'value' => $gen_set->anon_prefix,
);
$id='anon_prefix';
$this->tableGateway->update($data,array('settings' => $id));
}
}
I am getting this error, 'Cannot use object of type Chat\Model\Chat as array'
Your action doesn't make much sense as it is, you instantiate a Chat instance as $model and later another instance as $gen_set. What you should be doing is binding the first one, and using the form class getData method to later return the instance you bound to it, along with the values you gave it in the setData method. There's no need for any transformations from object to array and back again.
Here's how it should look ...
public function indexAction()
{
$form = new ChatForm();
// bind the model
$model= new Chat();
$form->bind($model);
$form->get('submit')->setValue('Save');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($gen_set->getInputFilter());
// set data from POST as properties of the bound model ...
$form->setData($request->getPost());
if ($form->isValid()) {
// get the bound model instance with the POSTed values
// ($gen_set is now the original $model object instance bound above)
$gen_set = $form->getData();
// and save it
$this->getChatTable()->saveChat($gen_set);
// Redirect to list of albums
return $this->redirect()->toRoute('chat');
}
}
return array('form' => $form);
}
Controller Code -
<?php
class ChatController extends AbstractActionController {
protected $chatTable;
public function indexAction() {
$model = $this->getChatTable()->fetchLastChat();
if($model === null || $model->count() == 0)
$model = new Chat();
//Now if no record exists in the database then $model will be empty
//Else $model will contain data of last record.
$form = new ChatForm();
$form->bind($model);
$form->get('submit')->setValue('Save');
$request = $this->getRequest();
if ($request->isPost()) {
$gen_set = new Chat();
$form->setInputFilter($gen_set->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$gen_set->exchangeArray($form->getData());
$this->getChatTable()->saveChat($gen_set);
}
}
return array('form' => $form);
}
public function getChatTable() {
if (!$this->chatTable) {
$sm = $this->getServiceLocator();
$this->chatTable = $sm->get('Chat\Model\ChatTable');
}
return $this->chatTable;
}
}
ChatTable Class Code -
<?php
//Other use statements
use Zend\Db\Sql\Select;
class ChatTable {
protected $tableGateway;
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
}
public function fetchAll() {
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function fetchLastChat() {
$select = new Select('TABLE_NAME'); //Change the tablename accordingly
$select->order('PRIMARY_KEY DESC'); //Set the Primary Key of the table
$select->limit(1);
$resultSet = $this->tableGateway->selectWith($select);
return $resultSet->current();
}
//Rest of the Code ....
Please take the idea from the above code.

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