I try to upload a file with ZF2, but the file doesn't get uploaded. The form seems to be valid. Is there anything wrong with my code?
If I upload the image with php move_uploaded_files, everything work as expected.
Form:
class LogoUploadForm extends Form {
public function init() {
$this->add([
'type' => 'File',
'name' => 'logo',
'attributes' => array(
'id' => 'logo-input'
),
]);
}
}
InputFilter:
class LogoInputFilter extends InputFilter {
public function init() {
$this->add([
'name' => 'logo',
'required' => true,
'filters' => [
[
'name' => 'File\RenameUpload',
'options' => [
'target' => './data/upload/',
'overwrite' => true,
'randomize' => true,
'use_upload_extension' => true,
]
]
],
'validators' => [
[
'name' => 'File\MimeType',
'options' => [
'magicFile' => false, //Important: https://github.com/zendframework/zf2/issues/6493
'mimeType' => ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']
]
],
[
'name' => 'File\UploadFile',
],
[
'name' => 'File\Size',
'options' => [
'max' => '4MB'
]
]
]
]);
}
}
Controller:
class LogoController extends AbstractActionController {
public function uploadAction() {
$form = $this->logoUploadForm;
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData(array_merge_recursive(
$request->getPost()->toArray(),
$request->getFiles()->toArray()
));
if ($form->isValid()) {
exit('valid');
} else {
exit('invalid');
}
}
}
}
You should call in the view the prepare function before the form opentag function:
$form->prepare();
echo $this->form()->openTag($form);
The form will detect, at the moment you call the prepare function, the proper form encoding type to be used.
Related
I am working with Laminas DoctrineObjectInputFilter and want to get value of other property in Callback input filter like this code is in init function of Filter class which extends DoctrineObjectInputFilter
// input filter whose value is required
$this->add([
'name' => 'name',
'allow_empty' => false,
'filters' => []
]);
// Input filter in which I want value of input name
$this->add([
'name' => 'value',
'allow_empty' => true,
'filters' => [
[
'name' => 'Callback',
'options' => [
'callback' => function ($value) {
$name = // want to get property name value here
if (key_exists($name, $this->applicationConfig) && gettype($value) === 'string') {
return trim(strip_tags($value));
}
else {
return trim($value);
}
return $value;
},
],
],
],
]);
have checked $this->getRawValues() but its returning null for all inputs.
a bit late but I guess you 're searching for $context. Since the value of name is in the same InputFilter instance, you can simply use $context in your callback function.
<?php
declare(strict_types=1);
namespace Marcel\InputFilter;
use Laminas\Filter\StringTrim;
use Laminas\Filter\StripTags;
use Laminas\Filter\ToNull;
use Laminas\InputFilter\InputFilter;
use Laminas\Validator\Callback;
class ExampleInputFilter extends InputFilter
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'allow_empty' => false,
'filters' => [
[ 'name' => StripTags::class ],
[ 'name' => StringTrim::class ],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
]);
$this->add([
'name' => 'value',
'required' => true,
'allow_empty' => true,
'filters' => [],
'validators' => [
[
'name' => Callback::class,
'options' => [
'callback' => function($value, $context) {
$name = $context['name'];
...
},
],
],
],
]);
}
}
I have a form. The form has a Collection whose target element is a fieldset with a checkbox and a couple of text fields. The fieldset attached as the target element to Collection looks like this (simplified to avoid too much code):
class AFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(HydratorInterface $hydrator)
{
parent::__construct();
$this->setHydrator($hydrator)
->setObject(new SomeObject());
$this->add([
'type' => Hidden::class,
'name' => 'id',
]);
$this->add([
'type' => Checkbox::class,
'name' => 'selectedInForm',
]);
$this->add([
'type' => Text::class,
'name' => 'textField1',
]);
$this->add([
'type' => Text::class,
'name' => 'textField2',
]);
}
public function getInputFilterSpecification()
{
return [
'selectedInForm' => [
'required' => false,
'continue_if_empty' => true,
'validators' => [
['name' => Callback::class // + options for the validator],
],
],
'id' => [
'requred' => false,
'continue_if_empty' => true,
],
'textField1' => [
'required' => false,
'continue_if_empty' => true,
'validators' => [
['name' => SomeValidator::class],
],
],
'textField2' => [
'required' => true,
'validators' => [
['name' => SomeValidator::class],
],
],
],
}
}
I'd like to validate textField1 and textField2 based on if selectedInForm checkbox is checked in the form.
How could I do this?
I though of using a Callback validator for selectedInForm checkbox like this:
'callback' => function($value) {
if ($value) {
$this->get('textField1')->isValid();
// or $this->get('textField1')->getValue() and do some validation with it
}
}
but the problem with it is that, for some reason, the posted value of textField1 value isn't attached to the input yet. Same is true for textField2.
Two option is available. One is where you started, with callback validators.
The other one is to write a custom validator, and to make it reusable I recommend this solution.
<?php
use Zend\Validator\NotEmpty;
class IfSelectedInFormThanNotEmpty extends NotEmpty
{
public function isValid($value, array $context = null): bool
{
if (! empty($context['selectedInForm']) && $context['selectedInForm']) {
return parent::isValid($value);
}
return true;
}
}
And then you can use it as every other validator:
'textField2' => [
'required' => true,
'validators' => [
['name' => IfSelectedInFormThanNotEmpty::class],
],
],
This may not be your exact case, but I hope it helped to get the idea.
You may define options to make it more reusable with a configurable conditional field in public function __construct($options = null).
The problem
I have a Form and a FieldSet. I would like to validate that the FieldSet is not empty. Also, I want to validate each field in the FieldSet.
So far, whatever I have tried is validating one or the other, but not both. If elements is present in the Form's input filter specification, then it validates that elements is not empty, but does not validate the bar and baz fields of FieldSet. And, of course, the other way around. Any clue as to how to approach this issue would be much appreciated.
The Form
class FooForm extends Form implements InputFilterProviderInterface
{
public function init()
{
$this->add([
'name' => 'elements',
'type' => Collection::class,
'required' => true,
'options' => [
'target_element' => [
'type' => SomeElementFieldSet::class
]
]
]);
}
public function getInputFilterSpecification()
{
return [
[
'name' => 'elements',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
]
];
}
}
The FieldSet
class SomeElementFieldSet extends Fieldset implements InputFilterProviderInterface
{
public function init()
{
$this->add(['name' => 'bar']);
$this->add(['name' => 'baz']);
}
public function getInputFilterSpecification()
{
return [
[
'name' => 'bar',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
],
[
'name' => 'baz',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
]
];
}
}
Edit: Added full validation spec.
After getting some hints on Google and digging through the source code, I found a solution. Unfortunately the zend-inputfilter implementation is a little buggy and won't work nicely with getInputFilterSpecification(), but we can just construct our own InputFilter and return that directly:
The Form
class FooForm extends BaseForm
{
public function init()
{
$this->add([
'name' => 'elements',
'type' => Collection::class,
'options' => [
'target_element' => [
'type' => SomeElementFieldSet::class
]
]
]);
}
public function getInputFilter()
{
if (!$this->filter) {
$this->filter = new InputFilter();
/** #var Collection $elementsCollection */
$elementsCollection = $this->fieldsets['elements'];
/** #var SomeElementFieldSet $elementsFieldSet */
$elementsFieldSet = $elementsCollection->getTargetElement();
$collectionFilter = new CollectionInputFilter();
$collectionFilter->setIsRequired(true);
$collectionFilter->setInputFilter(
$elementsFieldSet->getInputFilterSpecification()
);
$this->filter->add($collectionFilter, 'elements');
}
return $this->filter;
}
}
This will validate that there is at least one element in the collection. And will validate all the elements one by one by the FieldSet's specification.
One problem persists, though. Whenever the collection is empty, the validation will return false, but will not return any messages. This is due to a bug in the zend-inputfilter component. Issues reported here and here. But that is another problem altogether.
Use setValidationGroup() method in the Form object by specifying an array of input fields you want to validate. Please refer to the Doc!
You may give a try this way. Though I have added some extra fields to the form for testing purpose only.
class FooForm extends Form implements InputFilterProviderInterface
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->add(['name' => 'title']);
$this->add([
'name' => 'elements',
'type' => Collection::class,
'required' => true,
'options' => [
'target_element' => [
'type' => SomeElementFieldSet::class,
],
],
]);
$this->add([
'type' => 'submit',
'name' => 'submit',
'attributes' => [
'value' => 'Post'
],
]);
// I pointed this. Here you can specify fields to be validated
$this->setValidationGroup([
'title',
'elements' => [
'bar',
],
]);
}
public function getInputFilterSpecification()
{
return [
[
'name' => 'title',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
],
];
}
}
And your fieldset class should be
class SomeElementFieldSet extends Fieldset implements InputFilterProviderInterface
{
public function init()
{
$this->add(['name' => 'bar']);
$this->add(['name' => 'baz']);
}
public function getInputFilterSpecification()
{
return [
[
'name' => 'bar',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
],
[
'name' => 'baz',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
]
];
}
}
Hope this would help!
I am new to Zend framework. I'm using custom validators to validate image file but I'm getting this error.
Zend\Validator\ValidatorPluginManager::get was unable to fetch or create an instance for ImageValidator
Here is validation file:
namespace User\Validator;
//use User\Validator\FileValidatorInterface;
use Zend\Validator\File\Extension;
use Zend\File\Transfer\Adapter\Http;
use Zend\Validator\File\FilesSize;
use Zend\Filter\File\Rename;
use Zend\Validator\File\MimeType;
use Zend\Validator\AbstractValidator;
class ImageValidator extends AbstractValidator
{
const FILE_EXTENSION_ERROR = 'invalidFileExtention';
const FILE_NAME_ERROR = 'invalidFileName';
const FILE_INVALID = 'invalidFile';
const FALSE_EXTENSION = 'fileExtensionFalse';
const NOT_FOUND = 'fileExtensionNotFound';
const TOO_BIG = 'fileFilesSizeTooBig';
const TOO_SMALL = 'fileFilesSizeTooSmall';
const NOT_READABLE = 'fileFilesSizeNotReadable';
public $minSize = 64; //KB
public $maxSize = 1024; //KB
public $overwrite = true;
public $newFileName = null;
public $uploadPath = './data/';
public $extensions = array('jpg', 'png', 'gif', 'jpeg');
public $mimeTypes = array(
'image/gif',
'image/jpg',
'image/png',
);
protected $messageTemplates = array(
self::FILE_EXTENSION_ERROR => "File extension is not correct",
self::FILE_NAME_ERROR => "File name is not correct",
self::FILE_INVALID => "File is not valid",
self::FALSE_EXTENSION => "File has an incorrect extension",
self::NOT_FOUND => "File is not readable or does not exist",
self::TOO_BIG => "All files in sum should have a maximum size of '%max%' but '%size%' were detected",
self::TOO_SMALL => "All files in sum should have a minimum size of '%min%' but '%size%' were detected",
self::NOT_READABLE => "One or more files can not be read",
);
protected $fileAdapter;
protected $validators;
protected $filters;
public function __construct($options)
{
$this->fileAdapter = new Http();
parent::__construct($options);
}
public function isValid($fileInput)
{
//validation code
}
}
and here is form file:
namespace User\Form;
use Zend\Form\Form;
use Zend\InputFilter\InputFilter;
use Zend\Session\Container;
use User\Validator\ImageValidator;
class PlatFormForm extends Form
{
public function __construct()
{
parent::__construct('platform-form');
$this->addInputFilter();
$this->setAttribute('method', 'post');
$this->setAttribute('enctype','multipart/form-data');
$this->setAttribute('class', 'platform_popup');
$this->addElements();
}
private function addElements()
{
$this->add([
'type' => 'file',
'name' => 'input_thumb',
'attributes' => [
'id' => 'input_thumb', 'class' => 'upload'
]
]);
$this->add([
'type' => 'submit',
'name' => 'submit',
'attributes' => [
'id' => 'add_platform', 'class' => 'upload'
],
'options' => [
'label' => 'CREATE',
//'label_attributes' => [
//],
],
]);
}
private function addInputFilter()
{
$inputFilter = new InputFilter();
$this->setInputFilter($inputFilter);
$inputFilter->add([
'name' => 'input_thumb',
'required' => true,
'filters' => [
['name' => 'StringTrim'],
['name' => 'StripTags'],
],
'validators' => [
[
'name' => 'ImageValidator',
'options' => [
'minSize' => '64',
'maxSize' => '1024',
'newFileName' => 'newFileName2',
'uploadPath' => './data/'
],
],
],
]
);
}
}
and here is module.config.php file code:
'validators' => array(
'invokables' => array(
'ImageValidator' => 'User\Validator\ImageValidator'
),
),
Can anyone suggest me what am I doing wrong?
To inject the validator options into your constructor method you should register your validator like this inside your module.config.php file:
<?php
use Zend\ServiceManager\Factory\InvokableFactory;
use User\Validator\ImageValidator;
return array(
//...
'validators' => array(
'factories' => array(
ImageValidator::class => InvokableFactory::class
),
'aliases' => array(
'ImageValidator' => ImageValidator::class
)
),
//...
);
Actually, I ran into the same problem, and first of all you don't need to inject anything ( unless you want to ). Just by adding the full path to the Validator will work, in your example:
private function addInputFilter()
{
$inputFilter = new InputFilter();
$this->setInputFilter($inputFilter);
$inputFilter->add([
'name' => 'input_thumb',
'required' => true,
'filters' => [
['name' => 'StringTrim'],
['name' => 'StripTags'],
],
'validators' => [
[
'name' =>'User\Validator\ImageValidator\ImageValidator',
'options' => [
'minSize' => '64',
'maxSize' => '1024',
'newFileName' => 'newFileName2',
'uploadPath' => './data/'
],
],
],
]
);
}
But I would like to point out that this is not the correct way of uploading files, regardless if they are images or not. You need to use FileInput() instead.
The simple example can be found here: https://framework.zend.com/manual/2.4/en/modules/zend.input-filter.file-input.html
I have a login Form LoginForm.php with its Filter LoginFilter.php, that has a View /login/index.phtml, a Controller LoginController.php, two Factory LoginControllerFactory.php & LoginFormFactory.php and it is called in the config.module.php and works perfect. The Form is correctly displayed.
I have a ViewController.php that has a method idAction that shows a post by its id passed by parameter from the homepage in a View called /view/id.phtml. I want to display this Form I created within this View and I don't know how. First, I created the Form exactly as I created the login Form, but I realized that I already configured my id child-route, inside of view route with a Factory in module.config.php.
Then, I tried to set the form in the idAction method, exactly as I did in indexAction in LoginController.php Controller, but I'm receiving the following error: An exception was raised while creating "Rxe\Factory\ViewController"; no instance returned.
I will now show you what I did to try to display this new Form.
First, the Form itself:
class CommentForm extends Form
{
public function buildForm()
{
$this->setAttribute('method', 'POST');
$this->setAttribute('id', 'add-comment-form');
$this->add(array(
'name' => 'comment',
'type' => 'textarea',
'options' => array(
'label' => 'Category'
),
'attributes' => array(
'class' => 'form-control'
)
));
$this->add(array(
'name' => 'submit',
'type' => 'submit',
'attributes' => array(
'class' => 'btn btn-success',
'value' => 'Comment'
)
));
}
}
Form's CommentFormFactory.php calling its Filter and building the Form:
class CommentFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$form = new CommentForm();
$form->setInputFilter($serviceLocator->get('Rxe\Factory\CommentFilter'));
$form->buildForm();
return $form;
}
}
The ViewControllerFactory.php calling the CommentFormFactory.php, just like in LoginControllerFactory.php:
class ViewControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
$viewController = new ViewController();
$viewController->setPostsTable($serviceManager->get('Rxe\Factory\PostsTable'));
$viewController->setCommentsTable($serviceManager->get('Rxe\Factory\CommentsTable'));
$viewController->setCommentForm($serviceManager->get('Rxe\Factory\CommentForm'));
return $viewController;
}
}
The ViewController.php, calling the form within its idAction's ViewModel:
class ViewController extends AbstractActionController
{
use PostsTableTrait;
use CommentsTableTrait;
private $commentForm;
function setCommentForm($commentForm)
{
$this->commentForm = $commentForm;
}
public function indexAction()
{
$category = $this->params()->fromRoute('category');
return new ViewModel(array(
'posts' => $this->postsTable->getPostsByCategory($category),
'categories' => $category
));
}
public function idAction()
{
$id = $this->params()->fromRoute('id');
$viewModel = new ViewModel(array(
'commentForm' => $this->commentForm,
'commentParams' => $this->params()->fromPost(),
'messages' => $this->flashMessenger()->getMessages(),
'posts' => $this->postsTable->getPostById($id),
'posts' => $this->commentsTable->getNumberOfCommentsByPost($id),
'comments' => $this->commentsTable->getCommentsByPost($id)
));
$viewModel->setTemplate('rxe/view/id.phtml');
if ($this->getRequest()->isPost()) {
$this->commentForm->setData($this->params()->fromPost());
if ($this->commentForm->isValid()) {
$this->flashMessenger()->addMessage('Thank you for your comment. :)');
} else {
$this->flashMessenger()->addMessage('Your comment wasn\'t sent.');
}
}
return $viewModel;
}
}
And finally my module.config.php
'controllers' => array(
'invokables' => array(
'Rxe\Controller\Index' => 'Rxe\Controller\IndexController',
'Rxe\Controller\View' => 'Rxe\Controller\ViewController',
'Rxe\Controller\Login' => 'Rxe\Controller\LoginController'
),
'factories' => array(
'Rxe\Factory\LoginController' => 'Rxe\Factory\LoginControllerFactory',
'Rxe\Factory\ViewController' => 'Rxe\Factory\ViewControllerFactory',
'Rxe\Factory\IndexController' => 'Rxe\Factory\IndexControllerFactory'
)
),
'service_manager' => array(
'factories' => array(
'Rxe\Factory\LoginForm' => 'Rxe\Factory\LoginFormFactory',
'Rxe\Factory\LoginFilter' => 'Rxe\Factory\LoginFilterFactory',
'Rxe\Factory\CommentForm' => 'Rxe\Factory\CommentFormFactory',
'Rxe\Factory\CommentFilter' => 'Rxe\Factory\CommentFilterFactory',
'Rxe\Factory\PostsTable' => 'Rxe\Factory\PostsTableFactory',
'Rxe\Factory\CategoriesTable' => 'Rxe\Factory\CategoriesTableFactory',
'Rxe\Factory\CommentsTable' => 'Rxe\Factory\CommentsTableFactory',
'Zend\Db\Adapter\AdapterService' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
),
Please, let me know if you need me to show you more codes. Thank you in advance.
EDIT #1
If I remove the line that calls the Form in the ViewControllerFactory.php, I get the following error: Fatal error: Call to a member function prepare() on a non-object in /home/vol12_3/byethost4.com/b4_16354889/htdocs/module/Rxe/view/rxe/view/id.phtml on line 31
The id.phtml is:
<!-- Comment form -->
<div id="comment-form-area" class="col-xs-3">
<?php $this->commentForm->prepare() ?>
<?php echo $this->form()->openTag($this->commentForm); ?>
<div class="form-group comment-area">
<?php echo $this->formRow($this->commentForm->get('comment_content')); ?>
</div>
<div class="form-group">
<?php echo $this->formRow($this->commentForm->get('submit')); ?>
</div>
<?php echo $this->form()->closeTag(); ?>
</div>
<!-- /Comment form -->
Try removing these lines
'invokables' => array(
'Rxe\Controller\Index' => 'Rxe\Controller\IndexController',
'Rxe\Controller\View' => 'Rxe\Controller\ViewController',
'Rxe\Controller\Login' => 'Rxe\Controller\LoginController'
),
If it doesn't work, have a look at this tutorial how to create proper controller factories and pass dependencies. https://samsonasik.wordpress.com/2015/03/31/zend-framework-2-using-__invokepluginmanager-manager-in-services-factory/
An example how I build my forms:
namespace Admin\Form;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
class ContentForm extends Form implements InputFilterProviderInterface
{
public function __construct()
{
parent::__construct("content");
}
public function init()
{
$this->setAttribute('method', 'post');
$this->add([
'type' => 'Zend\Form\Element\Text',
'name' => 'title',
'attributes' => [
'required' => true,
'size' => 40,
'id' => "seo-caption",
'placeholder' => 'Title',
],
'options' => [
'label' => 'Title',
],
]);
$this->add([
'type' => 'Zend\Form\Element\Text',
'name' => 'text',
'attributes' => [
'class' => 'ckeditor',
'rows' => 5,
'cols' => 80,
],
'options' => [
'label' => 'Text',
],
]);
}
public function getInputFilterSpecification()
{
return [
[
"name"=>"title",
"required" => true,
'filters' => [
['name' => 'StripTags'],
['name' => 'StringTrim'],
],
'validators' => [
['name' => 'NotEmpty'],
[
'name' => 'StringLength',
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
'max' => 200,
],
],
],
],
[
"name"=>"text",
"required" => true,
'filters' => [
['name' => 'StripTags'],
['name' => 'StringTrim'],
],
'validators' => [
['name' => 'NotEmpty'],
[
'name' => 'StringLength',
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
],
],
],
],
];
}
}
Than I create a Factory
namespace Admin\Factory\Controller;
use Admin\Controller\ContentController;
use Zend\Mvc\Controller\ControllerManager;
class ContentFormFactory
{
/**
* #{inheritDoc}
*/
public function __invoke(ControllerManager $controllerManager)
{
return new ContentController(
$controllerManager->getServiceLocator()->get('FormElementManager')->get('Admin\Form\ContentForm')
);
}
}
Inside module.config.php I have this code
'controllers' => [
'factories' => [
'Admin\Controller\Content' => "Admin\Factory\Controller\ContentFormFactory",
],
'invokables' => [
...
],
],
Please, show us some more code.