I am developing an REST API using Codeigniter.
I need to have all error messages from Form validation in array format so that
I can easily respond in either JSON or XML.
Right now Codeigniter is delivering error messages with <p> as delimiters (see below)
but that is not good for a REST based API.
<p>There was an error with this item</p>
How can I get errors in an array?
Thankful for all input!
The form validation library stores errors in an array and loops through them to generate the error string. They are stored in a private variable called $_error_array. You could extend the library with a simple method that returns the error array.
class MY_Form_validation extends CI_Form_validation
{
function get_error_array()
{
return $this->_error_array;
}
}
I assume you are familiar with extending core CI libraries, but this extension of the form validation library will give you a method to return the errors as an array with the name attribute's value as the key and the message as the value. I tested it out on one of my projects and it worked fine.
You can transform it easily:
/**
* #param $errors string
* #return array
*/
function transformErrorsToArray ($errors) {
$errors = explode('</p>', $errors);
foreach ($errors as $index => $error) {
$error = str_replace('<p>', '', $error);
$error = trim($error);
// ... more cleaning up if necessary
$errors[$index] = $error
}
return $errors;
}
Related
I have a Validation class and a failure method which takes 2 strings as parameters. My problem is that I have to go through all the ifs and finally put as parameters for this function, which fields have passed the validation and display the appropriate message. With the current code, unfortunately, it only displays a message for the last field that did not pass validation, and it should for all that did not pass. I will be grateful for your help.
class Validation
{
private function __construct(private array $fieldsAndMessages)
{
}
public static function failure(string $fieldName, string $message): Validation
{
return new self([$fieldName => $message]);
}
}
$field = '';
$message = '';
if ($request->settingsSubmit()) {
if ($request->wantsSubmitPhoto()) {
$extensionPhoto = new PhotoExtension($request->photo());
if (!in_array($extensionPhoto->getExtension(), ['jpg', 'png', 'jpeg'])) {
$field = 'photo';
$message = 'Invalid photo';
}
}
if ($request->wantsSubmitAvatar()) {
$extensionAvatar = new AvatarExtension($request->avatar());
if (!in_array($extensionAvatar->getExtension(), ['jpg', 'png', 'jpeg'])) {
$field .= 'avatar';
$message .= 'Invalid avatar';
}
return new SettingsView($userId, Validation::failure($field, $message));
UPDATE
as the OP has mentioned that he cannot modify the failure method (which should've done in the question itself), I will provide a potentially possible workaround.
The idea is to keep the Validation class as is without modifying it and instead you may have an array that will hold instances of the Validation class where each error found will create a new instance of Validation class.
/** this variable will hold all the potential errors where each error is an instance of Validation class */
$errors = [];
if ($request->settingsSubmit()) {
if ($request->wantsSubmitPhoto()) {
$extensionPhoto = new PhotoExtension($request->photo());
if (!in_array($extensionPhoto->getExtension(), ['jpg', 'png', 'jpeg']))
$errors[] = Validation::failure('photo', 'Invalid photo');
}
if ($request->wantsSubmitAvatar()) {
$extensionAvatar = new AvatarExtension($request->avatar());
if (!in_array($extensionAvatar->getExtension(), ['jpg', 'png', 'jpeg']))
$errors[] = Validation::failure('avatar', 'Invalid avatar');
}
}
/**
* pass the array ($errors) containing the errors to the SettingsView class.
* the "SettingsView" class should expect an array of `Validation` class as the second parameter.
*/
return new SettingsView($userId, $errors);
Original Answer
With your current implementation, you keep on overriding the $field and $message variables and you'll always have 0 or 1 error.
To allow having more than one error, you may tweak your Validation class' failure method and have it accept an array of error messages instead of a expecting a field and a message which will allow you to display all the found errors when your validation process fails.
The errors array that the failure method expects should have the following structure: the keys are the field name and the values are the actual messages (not required, I based that upon the usage of field names in your code and because you try to store the names of the fields having errors).
Here's an example of an error messages array that you may now pass to your method:
$errors = [
/** the keys are the fields | the values are the messages */
'photo' => 'Invalid photo',
'avatar' => 'Invalid avatar',
];
Your failure method could be refactored as the following to accept an array of error messages:
/** accepts an array of error messages where the keys act as the field names and the values as the actual error messages for the fields */
public static function failure(array $errors): self
{
/** pass the received array to the constructor */
return new self($errors);
}
An example of usage that is based on your validation flow could be as follows:
/** this variable will hold all the potential errors */
$errors = [];
if ($request->settingsSubmit()) {
if ($request->wantsSubmitPhoto()) {
$extensionPhoto = new PhotoExtension($request->photo());
if (!in_array($extensionPhoto->getExtension(), ['jpg', 'png', 'jpeg']))
$errors['photo'] = 'Invalid photo';
}
if ($request->wantsSubmitAvatar()) {
$extensionAvatar = new AvatarExtension($request->avatar());
if (!in_array($extensionAvatar->getExtension(), ['jpg', 'png', 'jpeg']))
$errors['avatar'] = 'Invalid avatar';
}
}
/** pass the array ($errors) containing the errors to the failure method */
return new SettingsView($userId, Validation::failure($errors));
Now your Validation class should have the errors in an array which you may later loop through those errors and display them for the user.
Disclaimer: The above code examples are not expected to work as is as they only meant to showcase the answer's idea and to demonstrate it. I recommend you take the idea from them and the above logic/explanations above and build upon. Also, that's one way of doing the things and it's definitely not the only possible way to get you on track.
I am building a Symfony data transformer class called EventDataMapper. It handles two fields: A TextType field called My mapDataToForms() definition looks like this:
public function mapDataToForms($data, $forms)
{
$existingTitle = $data->getTitle();
$existingAttendees = $data->getAttendees();
$this->propertyPathMapper->mapDataToForms($data, $forms);
foreach ($forms as $index => $form) {
if ($form->getName() === 'title' && !is_null($existingTitle)) {
$form->setData($existingTitle);
}
if ($form->getName() === 'attendees' && !is_null($existingAttendees)) {
$form->setData($existingAttendees);
}
}
}
The problem is that I'm setting data before validation runs, so if I submit a form with a non-numeric string in the "attendees" field, I get an ugly TransformationFailedException ('Unable to transform value for property path "attendees": Expected a numeric'). And if I try to do a check for whether my field is valid by adding a call to $form->isValid() in the line before I call $form->setData(), I get a LogicException. ('Cannot check if an unsubmitted form is valid. Call Form::isSubmitted() before Form::isValid().')
Is there any way for my to preemptively call a validator on this specific field from within my DataMapper?
(Yes, this can be somewhat prevented with frontend logic. But I don't want to rely too much on that.)
Closing the loop on this. Here's what we did.
A colleague made a new form type corresponding to a new adapter class that wraps our two previous classes, providing a uniform set of wrapper methods for interacting with them.
We passed Symfony's validator service into our new form type using the constructor.
In that form type, we're using $builder->addEventListener() to add a callback/listener on the POST_SUBMIT event. Here's the callback:
function(FormEvent $event): void {
$adapter = $event->getData();
$form = $event->getForm();
$errors = $adapter->propagate($this->validator);
foreach ($errors as $error) {
$formError = new FormError($error->getMessage());
$targetPath = self::mapPropertyPath($error->getPropertyPath());
$target = $targetPath !== null ? $form->get($targetPath) : $form;
$target->addError($formError);
}
}
The adapter, in turn, has some logic that does various translations of data into a form that can be used in our legacy classes, followed by this:
return $validator->validate($this->legacyObject);
This works well for us. I hope it helps somebody else out too.
I am using codeigniter 2.xx, I found out that on passing Arrays as Field Names in the html form it throws php and database errors. This is a serious vulnerability and what if an user does the same using chrome html debugging tool? It can cause security breach in the website! So, I was looking for a method to turn off the acceptance of Arrays as Field Names in codeigniter by default, but couldn't find it anywhere!
Is there any workaround with the core functionality so that form_validation, set_value() function and database drivers won't accept an array from the field name unless it is programmed for it?
Okay guys I didn't get any response for my question, So I came up with a work around. I made this small function which may get rid of input array injection.
1) Create a library file or a model. import it into the controller you wish to conduct this validation.
2) paste the following code into the model or library you just made:
/*
* Description: Checks for the array attacks in the input forms.
* #param $allowedArrayInputs, the input field name which may contain array values
* #param $method, post, get or request.
* #return boolean, in case no conflicts is detected else invoke error.
* */
public function array_inputs ($allowedArrayInputs = array (), $method = 'post') {
if ($method == 'request') {
$method_param = $_REQUEST;
}
elseif ($method == 'get') {
$method_param = $_GET;
}
else {
$method_param = $_POST;
}
foreach ($method_param as $key => $val2) {
if (is_array($val2)) {
if ($allowedArrayInputs && is_array($allowedArrayInputs)) {
if (in_array($key, array ( 'confirm_password' ))) {
continue;
}
}
show_error('User input was invalid. Try again!');
exit;
}
}
return true;
}
Hope this helps someone who is facing this similar problem!
Good day to all!
I need the Symfony2 Validator to return an array rather than an object.
So something like this:
$insert = new MyEntity();
$insert->setTest1( 'testtesttest' );
$validator = $this->get('validator');
$errors = $validator->validate($insert);
...would enable this:
$errors[0]['message'] = "The email is not valid"
Just a simple array as parsing the object returned is very difficult.
I understand the validator config, but I just need the Validator to return an array not its usual object.
I'm JSON encoding the result and (a) json_encode struggles with objects + (b) I don't want to return the whole object just a list of errors.
I'm not using the in-built forms, just the raw Validator.
You can loop over the objects to create an array of errors.
$errors = $this->get('validator')->validate( $insert );
$errorArray = array();
foreach($errors as $error)
{
$errorArray[$error->getPropertyPath()] = $error->getMessage();
}
Validator->validate() returns a object of ConstraintViolationListInterface, which implements the IteratorAggregate interface. Simple foreach over it and construct your desired array out of the ConstraintViolationInterface objects.
My crazy designer would like the message "Required" displaying (in red) inside a field if the form has been submitted and it is invalid because of an empty field.
The form in question is a login prompt and I'm using a custom class that extends sfGuardFormSignin
I've managed to set the value and add a class with..
$this->widgetSchema['username']->setAttribute('class','red');
$this->widgetSchema['username']->setDefault('Required');
..but how do I do this only when the username field is invalid and because of the Required error?
I assume it's the same for the password field?
Many thanks in advance
EDIT:
Thanks for the advice greg0ire. I've had a play with that but the formatRow method of sfWidgetFormSchemaFormatter doesn't seem to be getting hit. Is this because my form extends sfGuardFormSignin and using the sfGuardAuth plugin?
class FrontendsfGuardFormSignin extends sfGuardFormSignin
{
public function configure()
{
parent::configure();
// This works!
$this->widgetSchema['username']->setLabel('Email');
// I copied this from the link you pasted
$decorator = new myWidgetFormSchemaFormatterCustom($this->getWidgetSchema());
$this->widgetSchema->addFormFormatter('custom', $decorator);
$this->widgetSchema->setFormFormatterName('custom');
}
}
/lib/widget/myWidgetFormSchemaFormatterCustom.class.php
class myWidgetFormSchemaFormatterCustom extends sfWidgetFormSchemaFormatter
{
public function __construct(sfWidgetFormSchema $widgetSchema)
{
parent::__construct($widgetSchema);
}
public function formatRow($label, $field, $errors = array(), $help = '', $hiddenFields = null)
{
// Nothing happens!?
var_dump($errors);
die();
parent::formatRow($label, $field, $errors, $help, $hiddenFields);
}
}
$widget->render(array('value' => $widget->getError()));
Designers have such crazy ideas...
You'll have to write a custom schema formatter to do this. You'll probably have to override the formatRow() method to achieve this.
Analyse the $errors array argument of this method, and if you spot the "Required" error in it, then do your special stuff. You won't need to use the code you posted in your question.