Modify input before validation on Laravel 5.1 - php

I'm trying to modify an user submitted input before validation success. I've followed this easy instructions, but when I test it on Laravel 5.1, It's not working. Am I doing something wrong?
This is my Request class on SSHAM\Http\Requests\UserCreateRequest.php
<?php
namespace SSHAM\Http\Requests;
use SSHAM\Http\Requests\Request;
class UserCreateRequest extends Request
{
// Some stuff not related with this problem
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
// Only for debug
$prova = $this->all();
echo "<pre>Inside Request - Before sanitize\n[" . $prova['public_key'] . "]</pre>\n";
// Call a function to sanitize user input
$this->sanitize();
// Only for debug
$prova = $this->all();
echo "<pre>Inside Request - After sanitize\n[" . $prova['public_key'] . "]</pre>\n";
return [
'username' => 'required|max:255|unique:users',
'public_key' => 'openssh_key:public',
];
}
/**
* Sanitizes user input. In special 'public_key' to remove carriage returns
*/
public function sanitize()
{
$input = $this->all();
// Removes carriage returns from 'public_key' input
$input['public_key'] = str_replace(["\n", "\t", "\r"], '', $input['public_key']);
$this->replace($input);
}
}
This is my custom validation rule on SSHAM\Providers\OpenSSHKeyValidatorServiceProvider.php
<?php
namespace SSHAM\Providers;
use Illuminate\Support\ServiceProvider;
class OpenSSHKeyValidatorServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
// Registering the validator extension with the validator factory
\Validator::extend('openssh_key', function ($attribute, $value, $parameters) {
// Some stuff not related with this problem
// Only for debug
echo "<pre>Inside Validator value\n[" . $value ."]</pre>\n";
dd();
return true;
});
}
// Some stuff not related with this problem
}
When I call for debugging I obtain this output:
Inside Request - Before sanitize
[blah
second line
third line]
Inside Request - After sanitize
[blah second line third line]
Inside Validator value
[blah
second line
third line]
Seems that sanitize() is working, but when value is treated on validation class it has not been sanitized.

This is a tricky one. I only figured out one way to achieve what you want.
The main point is, that it has no effect for the Validator if you change the Request Values in the rules() function.
You could do a workaround by adding a function to your UserCreateRequest:
protected function getValidatorInstance() {
$this->sanitize();
return parent::getValidatorInstance();
}
This overrides the parent's getValidatorInstance();
The parent's getValidatorInstance() method includes
return $factory->make(
$this->all(), $this->container->call([$this, 'rules']), $this->messages(), $this->attributes());
Which is reached before your code in the rules() function, so the old values (not affected by the changes in rules()) of $this->all() are used.
If you override that function in your own RequestClass you can manipulate the Request values before calling the actual parent's method.
UPDATE (L5.5)
If you are using the Controllers validate function you could do something like that:
$requestData = $request->all();
// modify somehow
$requestData['firstname'] = trim($requestData['firstname']);
$request->replace($requestData);
$values = $this->validate($request, $rules);

You can do this by modifying the request and setting the input value.
$request->request->set('key', 'value');
Or, if you prefer the request helper method.
request()->request->set('key', 'value');

If you are using a request MyClassRequest for keeping your validation then simply override all() method of Request class
public function all()
{
$attributes = parent::all();
//you can modify your inputs here before it is validated
$attribute['firstname'] = trim($attribute['firstname']);
$attribute['lastname'] = trim($attribute['lastname']);
return $attributes;
}
Hope this helps.

These answers no longer work for me in 5.5
you can use
protected function validationData()
{
$this->request->add([
'SomeField' => '..some code to modify it goes here'
]);
return $this->request->all();
}
the add method on request overwrites any existing input for that key.
You can see why this works in Illuminate\Foundation\Http\FormRequest, if you follow the trail
/**
* Get data to be validated from the request.
*
* #return array
*/
protected function validationData()
{
return $this->all();
}

You can use the prepareForValidation method
protected function prepareForValidation()
{
$this->merge(['field' => 'field value' ]) ;
}

Related

Laravel Validator Not Returning Key

I am creating a new API call for our project.
We have a table with different locales. Ex:
ID Code
1 fr_CA
2 en_CA
However, when we are calling the API to create Invoices, we do not want to send the id but the code.
Here's a sample of the object we are sending:
{
"locale_code": "fr_CA",
"billing_first_name": "David",
"billing_last_name": "Etc"
}
In our controller, we are modifying the locale_code to locale_id using a function with an extension of FormRequest:
// This function is our method in the controller
public function createInvoice(InvoiceCreateRequest $request)
{
$validated = $request->convertLocaleCodeToLocaleId()->validated();
}
// this function is part of ApiRequest which extend FormRequest
// InvoiceCreateRequest extend ApiRequest
// So it goes FormRequest -> ApiRequest -> InvoiceCreateRequest
public function convertLocaleCodeToLocaleId()
{
if(!$this->has('locale_code'))
return $this;
$localeCode = $this->input('locale_code');
if(empty($localeCode))
return $this['locale_id'] = NULL;
$locale = Locale::where(Locale::REFERENCE_COLUMN, $localeCode)->firstOrFail();
$this['locale_id'] = $locale['locale_id'];
return $this;
}
If we do a dump of $this->input('locale_id') inside the function, it return the proper ID (1). However, when it goes through validated();, it doesn't return locale_id even if it's part of the rules:
public function rules()
{
return [
'locale_id' => 'sometimes'
];
}
I also tried the function merge, add, set, etc and nothing work.
Any ideas?
The FormRequest will run before it ever gets to the controller. So trying to do this in the controller is not going to work.
The way you can do this is to use the prepareForValidation() method in the FormRequest class.
// InvoiceCreateRequest
protected function prepareForValidation()
{
// logic here
$this->merge([
'locale_id' => $localeId,
]);
}

Newbie question about controller and request in Laravel in my case

I am new in Laravel, what I try to achieve is very simple thing, I would like to use FormRequest provided by Laravel to do validation of the request, but I encounter some puzzles (which I am sure is easy things to solve if you are experienced in Laravel).
Here is what I tried:
I have route maps to controller:
Route::put('user/{name}', 'UserController#show');
I can get the name parameter in show function:
class UserController {
public function show($name)
{
// validtion rules to apply
...
}
}
I have validation rules to apply to the request, so I decided to create form request by php artisan make:request ShowRequest, which creates the form request class:
class ShowRequest extends FormRequest {
public function authorize()
{
return true;
}
public function rules()
{
return [
// my validation rules here
];
}
}
Since I have above request class, so I refactored the show function in controller to receive the ShowRequest .
class UserController {
public function show(ShowRequest $request)
{
// now I don't need validtion rules in this function
// but how can I access the 'name' parameter now
...
}
}
I have two questions to ask:
Inside the refactored show function, how can I now access the route parameter name ?
If we forget about the parameter is a name (please don't focus on what to validate for name, imaging it is an object or value to validate in general). How to add custom logic for handling validation error instead of using Laravel default behaviour. I want to inject code like dummy code below:
if (!$validator->pass())
{
//my custom code for handling validation failure
}
Where to put my custom code for handling validation error now? I mean I don't know where to have this logic, in controller? in the request class? how?
You still can add the parameter $name in the show() method of your controller as it's part of the routed url more than the validated form/data. (recommanded)
class UserController {
public function show(ShowRequest $request, $name)
{
//...
}
}
You can also access it from the request object
class UserController {
public function show(ShowRequest $request)
{
$request->input('name');
}
}
As for the error messages (not the exception) you can add the messages() method to your ShowRequest::class
class ShowRequest extends FormRequest
{
/**
* #return array
*/
public function messages()
{
return [
'name.required' => 'The name is required',
'name.numeric' => 'The name must be a number',
//...
];
}
}
If you instead need to validate that the name catched by the route is only composed of letter OR really exists as a field in your DB (like a slug of a post) you need to add some validation in your route declaration.
Setup a route that catches request only if it is composed of letters.
Route::get('/user/{name}', 'Controller#show')->where(['name' => '[a-z]+']);
Setup a route that catches request only if the "name" exists in DB:
User.php
Class User //..
{
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'name';
}
}
web.php
//
Route::get('/user/{user:name}', 'Controller#show');
And adapt your controller to take a user directly
class UserController {
public function show(ShowRequest $request, User $user)
{
//...
}
}
You can access the values of the Form Request using this
$validated = $request->validated();
The $validated will have all the values which had been validated by the FormRequest.
To answer your second question, if you want to throw custom validation, you can always use the following
throw ValidationException::withMessages(['name' => 'Something is wrong']);

how to use laravel's validation rule in custom validation rule?

I have input $data =['identifier' = 'xxxxxxxxxx'];, and want to save the encrypt($data['identifier']) to the table info primary id column.
I've to validate before save it. Rule unique:info, id isn't suitable here, so I want to write a custom validation rule. And in the custom validation rule, I encrypt() the value first, then use the unique validation rule.
I know how to write a custom validation rule, but how to use the unique validation rule in my custom validation rule?
Rules "unique" and "exists" use the DatabasePresenceVerifier class. So, you don't need to really extend the unique rule, just access this presence verifier. For instance:
Validator::extend('encrypted_unique', function ($attribute, $value, $parameters, $validator) {
list ($table, $column, $ignore_id) = $parameters; // or hard-coded if fixed
$count = $validator->getPresenceVerifier()->getCount($table, $column, encrypt($value), $ignore_id);
return $count === 0;
});
Then you can use it as usual:
'identifier' => "encrypted_unique:table,column,$this_id"
Suppose you have A ModuleRequest that validates your inputs,you can write this method in this class
protected function validationData()
{
$all = parent::validationData();
$all['email'] = encrypt($all['email']);
return $all;
}
Laravel has Custom Validation Rules (https://laravel.com/docs/8.x/validation#using-rule-objects)
For example I have a table named clients who has two unique fields ecnrypt using Laravel's encryption services (https://laravel.com/docs/8.x/encryption) and because its encrypted, i can't aply the unique directive of validation method (https://laravel.com/docs/8.x/validation#rule-unique). The fields are code_client and email
That's the reason of implements a Custom Validation Rules.
this Service has two methods: passes and message. The method passes take two variables: $attributes (take de field to validate) and $value (take de value of field), and return true or false. Te method message retrieve message in case of failure.
In clients example i mentioned, folow the next steps:
php artisan make:rule ValidateFieldsClients
in class that composer creates ValidateFieldsClients, I have to declare a method for validate the fields in passes, I use this method for validate both fields (code_client and email).
next i complete de method message to retrieve the issue to user in views
additionally i declare a property $field to identify what´s the field it have the errors
The class ValidateFieldsClients example:
/***/class ValidateFieldsClients implements Rule{protected $field; /**
* Create a new rule instance.
*
* #return void
*/
public function __construct()
{
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$clients = client::all();
$this->field = $attribute;
foreach ($clients as $client ) {
if ($value == Crypt::decryptString($client->$attribute)) return false;
}
return true;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return strtoupper($this->field).' exists, check.';
}
}
Then to validate I use Form Request Validation (https://laravel.com/docs/8.x/validation#form-request-validation)
php artisan make:request ClientRequest
And in the validate method of the recently created class:
class ClientRequest extends FormRequest
{ /**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true; }
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'code_client'=> ['required', new ValidateFieldsClients],
'email'=>['required', new ValidateFieldsClients],
];
}
Finally in controller:
public function store(ClientRequest $request)
{ $clientRequest = $request->validated();
foreach ($clientRequest as $key => $client) {
$encryptedClient[$key] = Crypt::encryptString($client);
}; client::create($encryptedClient+ [
'idorga' => 1,
'idcrea' => 1,
'idmodifica' => 1
]);
return redirect('clientes/create')->with('success', 'Registro creado correctamente');
//return redirect('cuadros')->with('success', 'Registro exitoso!');
}

How to modify request input after validation in laravel?

I found method Request::replace, that allows to replace input parameters in Request.
But currently i can see only one way to implement it - to write same replacing input code in every controller action.
Is it possible somehow to group code, that will be executed after request successful validation, but before controller action is started?
For example, i need to support ISO2 languages in my api, but under the hood, i have to transform them into legacy ones, that are really stored in the database. Currently i have this code in controller:
// Controller action context
$iso = $request->input('language');
$legacy = Language::iso2ToLegacy($iso);
$request->replace(['language' => $legacy]);
// Controller action code starts
I think what you're looking for is the passedValidation() method from the ValidatesWhenResolvedTrait trait
How to use it:
Create custom Request: php artisan make:request UpdateLanguageRequest
Put validation rules into the rules() method inside UpdateLanguageRequest class
Use passedValidation() method to make any actions on the Request object after successful validation
namespace App\Http\Requests;
use App\...\Language;
class UpdateLanguageRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
// here goes your rules, f.e.:
'language' => ['max:255']
];
}
protected function passedValidation()
{
$this->replace(['language' => Language::iso2ToLegacy($this->language)]);
}
}
Use UpdateLanguageRequest class in your Controller instead Request
public function someControllerMethod(UpdateLanguageRequest $request){
// the $request->language data was already modified at this point
}
*And maybe you want to use merge not replace method since replace will replace all other data in request and the merge method will replace only specific values
This solution worked for me based on Alexander Ivashchenko answer above:
<?php
namespace App\Http\Requests\User;
class UserUpdateRequest extends UserRequest
{
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules(): array
{
return [
'name'=>'required|string',
'email'=>'required|string|email',
'password'=>'min:8'
];
}
}
Our parent UserRequest class:
<?php
namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Hash;
abstract class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize(): bool
{
return true;
}
/**
* Handle a passed validation attempt.
*
* #return void
*/
protected function passedValidation()
{
if ($this->has('password')) {
$this->merge(
['password' => Hash::make($this->input('password'))]
);
}
}
public function validated(): array
{
if ($this->has('password')) {
return array_merge(parent::validated(), ['password' => $this->input('password')]);
}
return parent::validated();
}
}
I am overriding validated method also. If we access each input element individually his answer works but in order to use bulk assignment in our controllers as follow we need the validated overriding.
...
public function update(UserUpdateRequest $request, User $user): JsonResource
{
$user->update($request->validated());
...
}
...
This happens because validated method get the data directly from the Validator instead of the Request. Another possible solution could be a custom validator wit a DTO approach, but for simple stuff this above it's enough.
Is it possible somehow to group code, that will be executed after
request successful validation, but before controller action is
started?
You may do it using a middleware as validator, for example:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
class InputValidator
{
public function handle($request, Closure $next, $fullyQualifiedNameOfModel)
{
$model = app($fullyQualifiedNameOfModel);
$validator = app('validator')->make($request->input(), $model->rules($request));
if ($validator->fails()) {
return $this->response($request, $validator->errors());
}
return $next($request);
}
protected function response($request, $errors)
{
if($request->ajax()) {
return new JsonResponse($errors, 422);
}
return redirect()->back()->withErrors($errors)->withInput();
}
}
Add the following entry in the end of $routeMiddleware in App\Http\Kernel.php class:
'validator' => 'App\Http\Middleware\InputValidator'
Add the rules method in Eloquent Model for example, app\Product.php is model and the rules method is declared as given below:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules(\Illuminate\Http\Request $request)
{
return [
'title' => 'required|unique:products,title,'.$request->route()->parameter('id'),
'slug' => 'required|unique:products,slug,'.$request->route()->parameter('id'),
];
}
Declare the route like this:
$router->get('create', [
'uses' => 'ProductController#create',
'as' => 'Product.create',
'permission' => 'manage_tag',
'middleware' => 'validator:App\Product' // Fully qualified model name
]);
You may add more middleware using array for example:
'middleware' => ['auth', 'validator:App\Product']
This is a way to replace the FormRequest using a single middleware. I use this middleware with model name as argument to validate all my models using a single middleware instead of individual FormRequest class for each controller.
Here, validator is the middleware and App\Product is the model name which I pass as argument and from within the middleware I validate that model.
According to your question, the code inside your controller will be executed only after input validation passes, otherwise the redirect/ajax response will be done. For your specific reason, you may create a specific middleware. This is just an idea that could be used in your case IMO, I mean you can add code for replacing inputs in the specific middleware after validation passes.
Use merge instead of replace
$iso = $request->merge('language');
$legacy = Language::iso2ToLegacy($iso);
$request->merge(['language' => $legacy]);

Laravel & Mockery: Unit testing the update method without hitting the database

Alright so I'm pretty new to both unit testing, mockery and laravel. I'm trying to unit test my resource controller, but I'm stuck at the update function. Not sure if I'm doing something wrong or just thinking wrong.
Here's my controller:
class BooksController extends \BaseController {
// Change template.
protected $books;
public function __construct(Book $books)
{
$this->books = $books;
}
/**
* Store a newly created book in storage.
*
* #return Response
*/
public function store()
{
$data = Input::except(array('_token'));
$validator = Validator::make($data, Book::$rules);
if($validator->fails())
{
return Redirect::route('books.create')
->withErrors($validator->errors())
->withInput();
}
$this->books->create($data);
return Redirect::route('books.index');
}
/**
* Update the specified book in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
$book = $this->books->findOrFail($id);
$data = Input::except(array('_token', '_method'));
$validator = Validator::make($data, Book::$rules);
if($validator->fails())
{
// Change template.
return Redirect::route('books.edit', $id)->withErrors($validator->errors())->withInput();
}
$book->update($data);
return Redirect::route('books.show', $id);
}
}
And here are my tests:
public function testStore()
{
// Add title to Input to pass validation.
Input::replace(array('title' => 'asd', 'content' => ''));
// Use the mock object to avoid database hitting.
$this->mock
->shouldReceive('create')
->once()
->andReturn('truthy');
// Pass along input to the store function.
$this->action('POST', 'books.store', null, Input::all());
$this->assertRedirectedTo('books');
}
public function testUpdate()
{
Input::replace(array('title' => 'Test', 'content' => 'new content'));
$this->mock->shouldReceive('findOrFail')->once()->andReturn(new Book());
$this->mock->shouldReceive('update')->once()->andReturn('truthy');
$this->action('PUT', 'books.update', 1, Input::all());
$this->assertRedirectedTo('books/1');
}
The issue is, when I do it like this, I get Mockery\Exception\InvalidCountException: Method update() from Mockery_0_Book should be called exactly 1 times but called 0 times. because of the $book->update($data) in my controller. If I were to change it to $this->books->update($data), it would be mocked properly and the database wouldn't be touched, but it would update all my records when using the function from frontend.
I guess I simply just want to know how to mock the $book-object properly.
Am I clear enough? Let me know otherwise. Thanks!
Try mocking out the findOrFail method not to return a new Book, but to return a mock object instead that has an update method on it.
$mockBook = Mockery::mock('Book[update]');
$mockBook->shouldReceive('update')->once();
$this->mock->shouldReceive('findOrFail')->once()->andReturn($mockBook);
If your database is a managed dependency and you use mock in your test it causes brittle tests.
Don't mock manage dependencies.
Manage dependencies: dependencies that you have full control over.

Categories