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.
Related
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've searched for the answer to my problem, but I'm having trouble putting it to words. So... I'm asking here.
I have a method which is created to echo text with a given string and an error type:
public function output($string, $errorType) : void
{
echo "<div class='alert alert-$errorType' role='alert'>$string</div>";
}
Since I'm using bootstrap, this $errorType should always be warning, danger or success. But since it's a string, I can give $errorType an entirely different value. I want to force the usage of the three error types: warning, danger or success. What is the best way to do this?
This question is one of input validation.
In your context, there's several approaches you can take. The ultimate goal is to check if the input is in a list of accepted values, but the main question is where, when and how to do this.
The most intuitive approach would be to put it right inside the method, eg
if (!in_array($input, self::ACCEPTABLE_INPUT)) {
throw some exception
}
An alternative mechanism in the same spirit would be to use assert:
assert(in_array($input, self::ACCEPTABLE_INPUT));
However this assumes a decent test coverage.
One way which I generally tend to prefer is to model the error type as a value object, eg.
final class ErrorType
{
private const ACCEPTED = ['warning', 'danger', 'success'];
private $type;
public function __construct(string $type)
{
if (!in_array($type, ErrorType::ACCEPTED) {
throw some exception
}
$this->type = $type;
}
public function __toString(): string
{
return $this->type;
}
}
It's a bit more code, but allows you to follow the Single Responsibility Principle.
Your function would simply look like this:
public function output($string, ErrorType $errorType) : void
{
echo "<div class='alert alert-$errorType' role='alert'>$string</div>";
}
Throw an Exception if input value of $errorType is not a valid value. You can do something like as below:
public function output($string, $errorType) : void
{
// define all possible valid input values for $errorType
$accepted_error_type = array('warning', 'danger', 'success');
// throw exception if invalid input
if (!in_array($errorType, $accepted_error_type) {
throw new Exception('Invalid errorType in function output. Expected: warning, danger or success. Received: ' . $errorType);
}
// Rest of the implementation details come here
echo "<div class='alert alert-$errorType' role='alert'>$string</div>";
}
You can let users to give int as $errorType. For example 0 for warning, 1 for danger, 2 for success. For other values, you can either set a default value (for example warning) or you can return exception.
public function output($string, $errorType)
{
$type = "";
if($errorType == 0)
$type = "warning";
else if($errorType == 1)
$type = "danger";
else if($errorType == 2)
$type = "success";
else
{
// You can set a default error type here, if input is incorrect
// Or you can simply return an exception to inform user about limitations.
}
echo "<div class='alert alert-$type' role='alert'>$string</div>";
}
I would like to validate given lecture. There are some validation rules but I want to extend it somehow with validation function which takes 3 attributes as arguments and check others lectures in database and return valid if none of lectures overlap. Lectures have start, end DateTime attributes.
Validation function is
private function noOtherLecturesOnGivenTime($start, $end, $hall)
{
if(!$start instanceof Carbon) {
$start = new Carbon($start);
}
if(!$end instanceof Carbon) {
$end = new Carbon($end);
}
if(is_null($hall)) {
return true; // if there is no hall defined, all times are acceptable
}
if(!$hall instanceof Model) {
/** #var Hall $hall */
$hall = Hall::find($hall);
}
$overlappingLectures = $hall->lectures()->get()->filter(function ($lecture) use ($start, $end) {
// (Ts < e) and (Te > s)
return $start->lt($lecture->end) and $end->gt($lecture->start);
});
return $overlappingLectures->count() === 0;
}
I don't know how and where to put this function so Laravel's validator throws exception and I could specify error message with i18n.
Thanks
Check out the documentation about custom validation rules.
From creating the rule to specifiying error messages, there is a full exemple there :
Laravel custom validation rules documentation
For your case, you can fetch the parameters from the request in the validation method.
I think the validation rule will still occur even if other parameters aren't set, don't forget to check if they are in the request
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 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;
}