Laminas / ZF3: Add manually Error to a field - zend-framework

is it possible to add manually an Error Message to a Field after Field Validation and Input Filter ?
I would need it in case the Username and Password is wrong, to mark these Fields / Display the Error Messages.
obviously in ZF/ZF2 it was possible with $form->getElement('password')->addErrorMessage('The Entered Password is not Correct'); - but this doesnt work anymore in ZF3/Laminas

Without knowing how you do your validation (there are a few methods, actually), the cleanest solution is to set the error message while creating the inputFilter (and not to set it to the element after it has been added to the form).
Keep in mind that form configuration (elements, hydrators, filters, validators, messages) should be set on form creation and not in its usage.
Here the form is extended (with its inputfilter), as shown in the documentation:
use Laminas\Form\Form;
use Laminas\Form\Element;
use Laminas\InputFilter\InputFilterProviderInterface;
use Laminas\Validator\NotEmpty;
class Password extends Form implements InputFilterProviderInterface {
public function __construct($name = null, $options = []) {
parent::__construct($name, $options);
}
public function init() {
parent::init();
$this->add([
'name' => 'password',
'type' => Element\Password::class,
'options' => [
'label' => 'Password',
]
]);
}
public function getInputFilterSpecification() {
$inputFilter[] = [
'name' => 'password',
'required' => true,
'validators' => [
[
'name' => NotEmpty::class,
'options' => [
// Here you define your custom messages
'messages' => [
// You must specify which validator error messageyou are overriding
NotEmpty::IS_EMPTY => 'Hey, you forgot to type your password!'
]
]
]
]
];
return $inputFilter;
}
}
There are other way to create the form, but the solution is the same.
I also suggest you to take a look at the laminas-validator's documentation, you'll find a lot of useful informations

The Laminas\Form\Element class has a method named setMessages() which expects an array as parameter, for example
$form->get('password')
->setMessages(['The Entered Password is not Correct']);
Note that this will clear all error messages your element may already have. If you want to add your messages as in the old addErrorMessage() method you can do like so:
$myMessages = [
'The Entered Password is not Correct',
'..maybe a 2nd custom message'
];
$allMessages = array_merge(
$form->get('password')->getMessages(),
$myMessages);
$form
->get('password')
->setMessages($allMessages);
You can also use the error-template-name Laminas uses for its error messages as key in your messages-array to override a specific error message:
$myMessages = [
'notSame' => 'The Entered Password is not Correct'
];

Related

Data not posted for custom operation

I've got very big trouble with custom operations in Laravel backpack.
The documentated setup is clear but lack a real exemple with a form.
In my case I wanted to use the form engine to create a form for a relationship.
First step I did this :
public function getProtocoleForm($id)
{
// Config base
$this->crud->hasAccessOrFail('update');
$this->crud->setOperation('protocole');
//
$this->crud->addFields([
[ 'name' => 'codeCim',
'type' => 'text',
'label' => 'Code CIM',
],
]);
$this->crud->addSaveAction([
'name' => 'save_action_protocole',
'visible' => function($crud) {
return true;
},
'button_text' => 'Ajouter le procotole',
'redirect' => function($crud, $request, $itemId) {
return $crud->route;
},
]);
// get the info for that entry
$this->data['entry'] = $this->crud->getEntry($id);
$this->data['crud'] = $this->crud;
$this->data['saveAction'] = $this->crud->getSaveAction();
$this->data['title'] = 'Protocole ' . $this->crud->entity_name;
return view('vendor.backpack.crud.protocoleform', $this->data);
}
This is working fine, the form appears on the screen, then I did a setup for a post route like this :
Route::post($segment . '/{id}/protocolestore', [
'as' => $routeName . '.protocolestore',
'uses' => $controller . '#storeProtocole',
'operation' => 'protocole',
]);
The route appears correctly when I execute the artisan command but the storeProtocole function is never called. I checked the generated HTML and the form action is correct and checking in the "network" panel of the browser is also targeting the correct route.
Can you help me and tell me where I missed something ?
[Quick update] I made a mistake, the form route is not good in the HTML, it takes the route of the main controller.

TYPO3 TCA make the 'default' value dynamic

The title is rather self explanatory, but what i would like to have is a dynamic default value.
The idea behind it is to get the biggest number from a column in the database and then add one to the result. This result should be saved as the default value.
Lets take for example this code:
$GLOBALS['TCA'][$modelName]['columns']['autojobnumber'] = array(
'exclude' => true,
'label' => 'LLL:EXT:path/To/The/LLL:tx_extension_domain_model_job_autojobnumber',
'config' => [
'type' => 'input',
'size' => 10,
'eval' => 'trim,int',
'readOnly' =>1,
'default' => $result,
]
);
The SQL looks like this:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_extension_domain_model_job');
$getBiggestNumber = $queryBuilder
->select('autojobnumber')
->from('tx_extension_domain_model_job')
->groupBy('autojobnumber')
->orderBy('autojobnumber', 'DESC')
->setMaxResults(1)
->execute()
->fetchColumn(0);
$result = $getBiggestNumber + 1;
So how can i do that "clean"?
I thought about processCmdmap_preProcess but i dont know how to pass the value to the coorisponding TCA field. Plus i do not get any results on my backend when i use the DebuggerUtility like i get them when i use processDatamap_afterAllOperations after saving the Object.
Can someone point me to the right direction?
I don't think it is supported to create a dynamic default value, see default property of input field.
What you can do however, is to create your own type (use this instead of type="input"). You can use the "user" type. (It might also be possible to create your own renderType for type="input", I never did this, but created custom renderTypes for type= "select").
You can look at the code of InputTextElement, extend that or create your own from scratch.
core code: InputTextElement
documentation for user type
more examples in FormEngine documentation
Example
(slightly modified, from documentation)
ext_localconf.php:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][<current timestamp>] = [
'nodeName' => 'customInputField',
'priority' => 40,
'class' => \T3docs\Examples\Form\Element\CustomInputElement::class,
];
CustomInputElement
<?php
declare(strict_types = 1);
namespace Myvendor\MyExtension\Backend\FormEngine\Element\CustomInputElement;
use TYPO3\CMS\Backend\Form\Element\AbstractFormElement;
// extend from AbstractFormElement
// alternatively, extend from existing Type and extend it.
class CustomInputElement extends AbstractFormElement
{
public function render():array
{
$resultArray = $this->initializeResultArray();
// add some HTML
$resultArray['html'] = 'something ...';
// ... see docs + core for more info what you can set here!
return $resultArray;
}
}

Symfony form that generates another form

I have a controller action the prompts the user for some free text input. When submitted the text is parsed into some number of objects that I want to put out on another form for the user to confirm that the initial parsing was done correctly.
Normally after dealing with the response to a form submission we call $this->redirectToRoute() to go off to some other path but I have all these objects laying around that I want to use. If I redirect off someplace else I lose them.
How can I keep them? I tried building my new form right there in the controller action method but then its submission does not seem to be handled properly.
/**
* #Route( "/my_stuff/{id}/text_to_objects", name="text_to_objects" )
*/
public function textToObjects( Request $request, Category $category ) {
$form = $this->createForm( TextToObjectsFormType::class, [
'category' => $category,
]);
$form->handleRequest( $request );
if( $form->isSubmitted() && $form->isValid() ) {
$formData = $form->getData();
$allTheStuff = textParserForStuff( $formData['objectText'] );
$nextForm = $this->createForm( StuffConfirmationFormType::class, $allTheStuff );
return $this->render( 'my_stuff/confirmation.html.twig', [
'form' => $nextForm->createView(),
'category' => $category,
] );
}
return $this->render( 'my_stuff/text.html.twig', [
'form' => $form->createView(),
'category' => $category,
] );
}
This does fine to the point of displaying the confirmation form but when I submit that form I just end up displaying the original TextToObjects form?
To answer albert's question, the TextToObjectsFormType just has three fields, a way to set the date & time for the group of generated objects, a way to select the origin of the objects and a textarea for the textual description. I do not set a data_class so I get an associative array back with the submitted information.
class TextToObjectsFormType extends AbstractType {
public function buildForm( FormBuilderInterface $builder, array $options ) {
$builder
->add( 'textSourceDateTime', DateTimeType::class, [
'widget' => 'single_text',
'invalid_message' => 'Not a valid date and time',
'attr' => [ 'placeholder' => 'mm/dd/yyyy hh:mm',
'class' => 'js-datetimepicker', ],
])
->add( 'objectsOrigin', EntityType::class, [
'class' => ObjectSourcesClass::class,
])
->add( 'objectText', TextareaType::class, [
'label' => 'Copy and paste object description text here',
]);
}
}
How can I get the confirmed, potentially revised, objects back to put them into the database?
Thanks.
Using your current architecture
$nextForm = $this->createForm( StuffConfirmationFormType::class, $allTheStuff );
does not bear enough information. It should contain the action parameter to tell the submission to where route the post request.
In your StuffConfirmationFormType add an 'objectText' field hidden.
Create a confirmation_stuff route
Create an action for this route
In this action if the form is valid => save your stuff ELSE re render TextToObjectsFormType with an action linked to text_to_objects
Be aware that using this technique would not prevent a user to enter non functional data by manually editing the hidden field.
Hope this helps

Duplicate Validation on Combined Fields in zend form

Hi there I have a table in which combination of three fields is unique. I want to put the check of duplication on this combination. Table looks like
I know how to validate single field, But how to validate the combination is not know. To validate one field I use the following function
public function isValid($data) {
// Options for name field validation
$options = array(
'adapter' => Zend_Db_Table::getDefaultAdapter(),
'table' => 'currencies',
'field' => 'name',
'message'=> ('this currency name already exists in our DB'),
);
// Exclude if a id is given (edit action)
if (isset($data['id'])) {
$options['exclude'] = array('field' => 'id', 'value' => $data['id']);
}
// Validate that name is not already in use
$this->getElement('name')
->addValidator('Db_NoRecordExists', false, $options
);
return parent::isValid($data);
}
Will any body guide me how can I validate duplication on combined fields?
There is no ready to use validator for this, as far as I know. You have either to write your own, or do a check with SQL-query with three conditions (one for each field).
you have to Apply a validation on name element of zend form.
Here is code for add validation on name field.
$name->addValidator(
'Db_NoRecordExists',
true,
array(
'table' => 'currencies',
'field' => 'name',
'messages' => array( "recordFound" => "This Currency Name already exists in our DB") ,
)
);
And you must set required true.

Zend_Validate_Db_RecordExists against 2 fields

I usualy use Zend_Validate_Db_RecordExists to update or insert a record. This works fine with one field to check against. How to do it if you have two fields to check?
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => $this->_name,
'field' => 'id_sector,day_of_week'
)
);
if ($validator->isValid($fields_values['id_sector'],$fields_values['day_of_week'])){
//true
}
I tried it with an array and comma separated list, nothing works... Any help is welcome.
Regards
Andrea
To do this you would have to extend the Zend_Validate_Db_RecordExists class.
It doesn't currently know how to check for the existence of more than one field.
You could just use two different validator instances to check the two fields separately. This is the only work around that I can see right now besides extending it.
If you choose to extend it then you'll have to find some way of passing in all the fields to the constructor ( array seems like a good choice ), and then you'll have to dig into the method that creates the sql query. In this method you'll have to loop over the array of fields that were passed in to the constructor.
You should look into using the exclude parameter. Something like this should do what you want:
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => $this->_name,
'field' => 'id_sector',
'exclude' => array(
'field' => 'day_of_week',
'value' => $fields_values['day_of_week']
)
);
The exclude field will effectively add to the automatically generated WHERE part to create something equivalent to this:
WHERE `id_sector` = $fields_values['id_sector'] AND `day_of_week` = $fields_values['day_of_week']
Its kind of a hack in that we're using it for the opposite of what it was intended, but its working for me similar to this (I'm using it with Db_NoRecordExists).
Source: Zend_Validate_Db_NoRecordExists example
Sorry for the late reply.
The best option that worked for me is this:
// create an instance of the Zend_Validate_Db_RecordExists class
// pass in the database table name and the first field (as usual)...
$validator = new Zend_Validate_Db_RecordExists(array(
'table' => 'tablename',
'field' => 'first_field'
));
// reset the where clause used by Zend_Validate_Db_RecordExists
$validator->getSelect()->reset('where');
// set again the first field and the second field.
// :value is a named parameter that will be substituted
// by the value passed to the isValid method
$validator->getSelect()->where('first_field = ?', $first_field);
$validator->getSelect()->where('second_field = :value', $second_field);
// add your new record exist based on 2 fields validator to your element.
$element = new Zend_Form_Element_Text('element');
$element->addValidator($validator);
// add the validated element to the form.
$form->addElement($element);
I hope that will help someone :)
Although, I would strongly recommend a neater solution which would be to extend the Zend_Validate_Db_RecordExists class with the above code.
Enjoy!!
Rosario
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
'validators' => array('EmailAddress', $obj= new Zend_Validate_Db_NoRecordExists(array('adapter'=>$dbAdapter,
'field'=>'email',
'table'=>'user',
'exclude'=>array('field'=>'email','value'=>$this->_options['email'], 'field'=>'is_deleted', 'value'=>'1')
))),
For those using Zend 2, If you want to check if user with given id and email exists in table users, It is possible this way.
First, you create the select object that will be use as parameter for the Zend\Validator\Db\RecordExists object
$select = new Zend\Db\Sql\Select();
$select->from('users')
->where->equalTo('id', $user_id)
->where->equalTo('email', $email);
Now, create RecordExists object and check the existence this way
$validator = new Zend\Validator\Db\RecordExists($select);
$validator->setAdapter($dbAdapter);
if ($validator->isValid($username)) {
echo 'This user is valid';
} else {
//get and display errors
$messages = $validator->getMessages();
foreach ($messages as $message) {
echo "$message\n";
}
}
This sample is from ZF2 official doc
You can use the 'exclude' in this parameter pass the second clause that you want to filter through.
$clause = 'table.field2 = value';
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => 'table',
'field' => 'field1',
'exclude' => $clause
)
);
if ($validator->isValid('value') {
true;
}
I am using zend framework v.3 and validation via InputFilter(), it uses same validation rules as zend framework 2.
In my case I need to check, if location exists in db (by 'id' field) and has needed company's id ('company_id' field).
I implemented it in next way:
$clause = new Operator('company_id', Operator::OP_EQ, $companyId);
$inputFilter->add([
'name' => 'location_id',
'required' => false,
'filters' => [
['name' => 'StringTrim'],
['name' => 'ToInt'],
],
'validators' => [
[
'name' => 'Int',
],
[
'name' => 'dbRecordExists',
'options' => [
'adapter' => $dbAdapterCore,
'table' => 'locations',
'field' => 'id',
'exclude' => $clause,
'messages' => [
'noRecordFound' => "Location does not exist.",
],
]
],
],
]);
In this case validation will pass, only if 'locations' table has item with columns id == $value and company_id == $companyId, like next:
select * from location where id = ? AND company_id = ?