Laravel 5 - sanitize certain inputs on validation - laravel

Is it possible to sanitize certain inputs on (or before validation)?
I have this rules for now (as example):
public function rules()
{
return [
'name' => 'required|min:3|max:255|string',
'description' => 'string',
'hours.*' => ['required', new Hours],
];
}
I want to sanitize name and description, but I don't to sanitize hours. Is this possible to do on validation, or I have to sanitize after validation and before insert?

I don't recommend to do sanitization after validation.
Suppose we have a blog system and guests/users can comment on posts, and the minimum characters in a comment is 10
If someone comments with thank you (there's a trailing space), it will be passed at the validation level because it's a 10 chars comment, but when we sanitize the comment, say we trim it, it will be left with 9 chars. This is a silly example but can be something very important in your business logic somewhere.
Sanitize then validate.
To do request inputs sanitization, this article has a good pattern to do it https://medium.com/#melihovv/how-to-sanitize-input-data-in-declarative-manner-in-laravel-e4486068f751

To cannot do this before validation as the original value will still be there in request. It can be easily dealt with mutators.
You can use Eloquent's Mutators to format attribute values in Eloquent Model before insert or update.
To define mutator, add set prefix and attribute suffix with the attribute's name. Like following
// Name mutator
public function setNameAttribute($name)
{
$this->attributes['name'] = strtolower($name); // used strtolower just to show the mutation
}
// Description mutator
public function setDescriptinAttribute($description)
{
$this->attributes['description'] = strtolower($description); // used strtolower just to show the mutation
}
Check documentation for more info https://laravel.com/docs/5.6/eloquent-mutators#defining-a-mutator

The only way I could think of validating the input like that would be validating it on the client side. So basically using Javascript to validate it. If the user for some reason disables Javascript your server site validation would work. What you could try as well would be to post the data asynchronous using Ajax for instance and immidietly return an error message if the validation fails, this approach would be server side.

test this :
web.php
Route::get('test/', 'TestController#test');
TestController class :
<?php
namespace App\Http\Controllers;
use App\Http\Requests\PostRequest;
use App\Post;
class PostController extends Controller
{
public function test(PostRequest $request)
{
dd($request->get('name'));
}
}
PostRequest class :
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PostRequest 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()
{
$this->sanitize();
return [
'name'=>'required',
'description'=>'required',
'description'=>'required',
];
}
public function sanitize()
{
$input = $this->all();
$input['name'] = $input['name'].'some change';
$input['description'] = $input['description'].'another change';
$this->replace($input);
}
}
As you can see, I defined a method called sanitize in PostRequest
Now when I set the value of the name to the ‍‍ali in URL, but the ali some change value will be printed.
You can also use the daylerees / sanitizer package.

Related

How to validate related objects request data from a parent controller in Laravel?

I'm using Laravel 8.x and have a one-to-one and one-to-many relationship with models as follows.
class ServiceProvider extends Model
{
use HasFactory;
protected $guarded = [];
public function contact() {
return $this->hasOne('App\Models\Contact');
}
public function services() {
return $this->hasMany('App\Models\Service');
}
}
I'm using a single form to get all the data. The problem is both Contact and Service has their own validation to be done. I can duplicate the validation in the ServiceProviderController. But it seems ugly and violate DRY. Is there a way to call the ContactController and ServiceController to do the validation and return the validation result to be accessed by the ServiceProviderController?
As I guess you are doing something like:
/**
* Store a new service provider.
*
* #param Request $request
* #return Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('services/create')
->withErrors($validator)
->withInput();
}
// Store The Service Provider...
}
If Yes, I would recommend using a form request validator to validate the request. So if the request is get passed your controller will be called. Also, you can use the same Request Validation rule for both controllers. you can read how to create and use one more here.
You can set up enumerator classes that will return validation rules for you, or even declare static properties on your model which will hold an array of validation rules, and which can be called like: ServiceProvider::$rules or something similar.
This way you will keep all your rules at one place. You can't explicitly call controllers whenever, they respond to routes.
When you get the validation rules, just use $request->validate() method and send the rules you gathered to it.

Controller Method not called with custom Form Request Method

For form validation I made a Request class via php artisan make:request UpdatePlanRequest.
However after using the UpdatePlanRequest class in store the method isn't called anymore.
The UpdatePlanRequest:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePlanRequest 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()
{ //TODO: CHECK IF THE PROTOTYPE IDS ARE OWNED BY THE USER (https://stackoverflow.com/questions/42662579/validate-an-array-of-integers/42693970)
return [
'start_date' => 'required|date',
'end_date' => 'required|date|after:start_date',
'name' => 'required|string'
];
}
}
The controller method:
use App\Http\Requests\UpdatePlanRequest;
public function store(UpdatePlanRequest $request)
{
//
dd('hello');
}
If the function header is store(Request $request) hello is shown, in that example it isn't.
The custom Request class is necessary to call $request->validated(); later for validation purposes according to the docs.
Is there a reason you have your Request class as being abstract? The default class that is created when running php artisan make:request <name> doesn't define the class as being abstract. This seems to work for me, but not when declaring it as abstract.
$request->validated(); is used to retrieve the validated inputs, so just by calling the UpdatePlanRequest it should validate the request
//Try This
use App\Http\Requests\UpdatePlanRequest;
public function store(UpdatePlanRequest $request)
{
$validatedData = $request->validated();
dd('$validatedData');
$profile = new Profile([
'user_id' => $request->get('user_id'),
]);
$profile->save();
echo $request->session()->flash('alert-success', 'Profile details Succefully Added!');
return redirect('create')->with('success', 'Data saved!');
}
Your route will be.
Route::get('profile','ProfileController#store');
Route::post('profile/create','ProfileController#store')->name('create');
Well this works right!
When the method is called, it checks the request class (UpdatePlanRequest). If there is an error, it does not enter the method anymore and you can not see the output of dd() function.
If the data is correct after checking the rules, then dd() will be displayed.
You must manage errors

Laravel 5 Form request validation with parameters

I am using form request validation and there are some rules that needs external values as a parameters.
Here are my validation rules for editing a business profile inside a form request class,
public function rules()
{
return [
'name' => 'required|unique:businesses,name,'.$business->id,
'url' => 'required|url|unique:businesses'
];
}
I can use this on the controller by type hinting it.
public function postBusinessEdit(BusinessEditRequest $request, Business $business)
{
//
}
But how to pass the $business object as a parameter to the rules method?
Lets say this is your model binding:
$router->model('business', 'App\Business');
Then you can reference the Business class from within the FormRequest object like this:
public function rules()
{
$business = $this->route()->getParameter('business');
// rest of the code
}
Note that if you use your form request both for create and update validation, while creating the record, the business variable will be null because your object does not exists yet. So take care to make the needed checks before referencing the object properties or methods.
There can be many ways to achieve this. I do it as below.
You can have a hidden field 'id' in your business form like bellow,
{!! Form::hidden('id', $business->id) !!}
and you can retrieve this id in FormRequest as below,
public function rules()
{
$businessId = $this->input('id');
return [
'name' => 'required|unique:businesses,name,'.$businessId,
'url' => 'required|url|unique:businesses'
];
}
For those who switched to laravel 5 :
public function rules()
{
$business = $this->route('business');
// rest of the code
}
Let say if we have a scenario like we want to change our validation rules depends on the type that we pass in with the route. For example:
app.dev/business/{type}
For different type of business, we have different validation rules. All we need to do is type-hint the request on your controller method.
public function store(StoreBusiness $request)
{
// The incoming request is valid...
}
For the custom form request
class StoreBussiness extends FormRequest
{
public function rules()
{
$type = $this->route()->parameter('type');
$rules = [];
if ($type === 'a') {
}
return rules;
}
}
In Laravel 5.5 at least (haven't checked older versions), once you did your explicit binding (https://laravel.com/docs/5.5/routing#route-model-binding), you can get your model directly through $this:
class StoreBussiness extends FormRequest
{
public function rules()
{
$rules = [];
if ($this->type === 'a') {
}
return rules;
}
}
Since Laravel 5.6 you may type hint it in the rules method:
public function rules(Business $business)
{
return [
'name' => 'required|unique:businesses,name,'.$business->id,
'url' => 'required|url|unique:businesses'
];
}
See the docs for more:
You may type-hint any dependencies you need within the rules method's signature. They will automatically be resolved via the Laravel service container.

Dynamically hide certain columns when returning an Eloquent object as JSON?

How do dynamically hide certain columns when returning an Eloquent object as JSON? E.g. to hide the 'password' column:
$users = User::all();
return Response::json($users);
I'm aware I can set protected properties in the model ($hidden or $visible), but how do I set these dynamically? I might want to hide or show different columns in different contexts.
$model->getHidden();
$model->setHidden(array $columns);
$model->setVisible(array $columns);
From Lavarel 5.3 Documentation :
Temporarily Modifying Attribute Visibility
If you would like to make some typically hidden attributes visible on a given model instance, you may use the makeVisible method. The makeVisible method returns the model instance for convenient method chaining:
return $user->makeVisible('attribute')->toArray();
Likewise, if you would like to make some typically visible attributes hidden on a given model instance, you may use the makeHidden method.
return $user->makeHidden('attribute')->toArray();
I've found a complete solution around the problem with using $model->setHidden(array $columns);
Lets say, for example, that you would like to decide in the controller exactly which fields to return. Updating only the model's hidden forces you to go over each model before you return an array of models for example. The problem becomes even worse when those models have relationships that you would also like to change. You have to loop over each model, set the hidden attribute, and then for each also set the relationships hidden. What a mess.
My solution involves creating a static member for each model that when present, updates the visible/hidden attribute just before the call to "toArray":
<?php
trait DynamicHiddenVisible {
public static $_hidden = null;
public static $_visible = null;
public static function setStaticHidden(array $value) {
self::$_hidden = $value;
return self::$_hidden;
}
public static function getStaticHidden() {
return self::$_hidden;
}
public static function setStaticVisible(array $value) {
self::$_visible = $value;
return self::$_visible;
}
public static function getStaticVisible() {
return self::$_visible;
}
public static function getDefaultHidden() {
return with(new static)->getHidden();
}
public static function geDefaultVisible() {
return with(new static)->getVisible();
}
public function toArray() {
if (self::getStaticVisible())
$this->visible = self::getStaticVisible();
else if (self::getStaticHidden())
$this->hidden = self::getStaticHidden();
return parent::toArray();
}
}
As an added bonus, I expose a way to the model's default hidden/visible that you may have set in your model's class.
Don't to forget to add the trait
class Client extends Eloquent {
use DynamicHiddenVisible;
}
Finally, in the controller, before returning your model, decide on visible/hidden attributes:
public function getIndex($clientId) {
// in this specific call, I would like to hide the "special_type" field of my Client model
$hiddenFields = Client::getDefaultHidden();
array_push($hiddenFields, "special_type");
Client::setStaticHidden($hiddenFields);
return Client::find($clientId)->toJson();
}
I don't believe it is the job of the ORM to worry about presentation logic, and that is what JSON is. You'll aways need to cast data to various types as well as hide things and sometimes create a buffer zone to rename things safely.
You can do all of that with Fractal which I built for exactly this reason.
<?php namespace App\Transformer;
use Acme\Model\Book;
use League\Fractal\TransformerAbstract;
class BookTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'author'
];
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
/**
* Include Author
*
* #return League\Fractal\ItemResource
*/
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
Embedding (including) stuff might be a bit more than you need right now, but it can be very handy too.
In 5.4 you can hide and show attributes dinamically:
$model->makeVisible('attribute');
$model->makeHidden('attribute');
Laravel docs
In addition to #deczo's answer - I feel the $hidden variable is not really designed to be used dynamically. It is more to protect specific data from ever been incorrectly displayed (such as 'password').
If you want specific columns - you should probably just be using a select statement and just get the specific columns you want.
For Laravel 5.3 or greater version,
If you want to make multiple attributes temporary hidden or visible using single statement, you may use model->makeVisible() and model->makeHidden() methods with passing array of attributes.
For example, to hide multiple attributes,
$user->makeHidden(["attribute1", "attribute2", "attribute3"]);
And to make visible multiple attributes,
$user->makeVisible(["otherAttribute1", "otherAttribute2", "otherAttribute3"]);
In the Model:
protected $hidden = [
'your_field_1',
'your_field_2',
];
You can override the getHidden method in order to hide certain columns dynamically:
class FooModel extends Model
{
public function getHidden()
{
// do here your validations and return
// the columns names with the specific criteria
// you need
return ['columnName1', 'columnName2'];
}
}
Made a package for this that uses Model Policies.
https://github.com/salomoni/authorized-attributes
Use the Salomoni\AuthorizedAttributes trait
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Salomoni\AuthorizedAttributes;
class Post extends Model
{
use AuthorizedAttributes;
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = ['author_comments'];
}
Create and register a model policy. Add methods for the hidden attributes in camel-case prefixed with see.
namespace App\Policies;
use App\User;
class PostPolicy
{
/**
* Determine if a post author_comments-atrribute can be seen by the user.
*
* #param \App\User $user
* #return bool
*/
public function seeAuthorComments(User $user)
{
return $user->isAuthor();
}
}

Is it possible to alter error message in Laravel's Validator callback?

I have a custom validator set-up like this:
Validator::extend('valid_username', 'ProfileController#valid_username');
Then I have the following method which handles the validation. It checks both if the username already exists, and if the username contains valid characters.
public function valid_username($attribute, $value, $parameters)
{
$u = User::where('username', $value)->get();
if ($u->count())
{
// here I would like to return "Username already taken."
return FALSE;
}
else if (preg_match("/^[A-Za-z0-9#\.\-_]+$/", $value))
{
return TRUE;
}
else
{
// here I would like to return "Username contains invalid characters."
return FALSE;
}
}
I would like to alter the error message returned by this validator depending on which error caused the validation to fail. However, I don't know how to do this. In my language files I have set up the following line for the validator:
"valid_username" => "This username is already taken or contains invalid characters."
Is it possible with Laravel to return a specific error message? Or do I have to split this validation up in two custom validation rules? This might not be a problem in this case, but especially if database access is involved I would prefer to validate a retrieved Eloquent model in one validator instead of instantiating an Eloquent object twice.
After consulting the code, the answer is "not out of the box". You can, however, extend everything and make that work.
The process, which I don't have time to completely do at the moment (sorry!), would be to create a class extending Validator, making that functionality work, and then using a new ServiceProvider to replace Laravel's $app['validator'] with your own.
That process, a little more concretely, goes like this something like this:
<?php namespace MyLib\Validation;
class Validator extends \Illuminate\Validation\Validator {
// Fancy validation logic to be able to set custom messages
}
Then, you need to extend the Factory to return your new Validator:
<?php namespace MyLib\Validation;
class Factory extends \Illuminate\Validation\Factory {
// Change this method
/**
* Resolve a new Validator instance.
*
* #param array $data
* #param array $rules
* #param array $messages
* #return \MyLib\Validation\Validator
*/
protected function resolve($data, $rules, $messages)
{
if (is_null($this->resolver))
{
// THIS WILL NOW RETURN YOUR NEW SERVICE PROVIDER SINCE YOU'RE
// IN THE MyLib\Validation NAMESPACE
return new Validator($this->translator, $data, $rules, $messages);
}
else
{
return call_user_func($this->resolver, $this->translator, $data, $rules, $messages);
}
}
}
...and finally, extend the Validation service provider, use your new Factory, and then replace the default ValidationServiceProvider with your own.
<?php namespace MyLib\Validation;
class ValidationServiceProvider extends \Illuminate\Validation\ServiceProvider {
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->registerPresenceVerifier();
$this->app['validator'] = $this->app->share(function($app)
{
// THIS WILL NOW RETURN YOUR FACTORY SINCE YOU'RE
// IN THE MyLib\Validation NAMESPACE
$validator = new Factory($app['translator'], $app);
// The validation presence verifier is responsible for determining the existence
// of values in a given data collection, typically a relational database or
// other persistent data stores. And it is used to check for uniqueness.
if (isset($app['validation.presence']))
{
$validator->setPresenceVerifier($app['validation.presence']);
}
return $validator;
});
}
}
So anyway, that's one way to extend the Validation library with your own code. I didn't solve the issue of adding your own messages, but this will show you how, if you can read the core code and see how to add that functionality in, to go about making it work in your app.
Last note:
You may want to see how Laravel handles using Database "stuff" within validation rules - While this may not affect your application (unless it gets big!) you may want to consider using a Repository pattern of some sort and using that in your Validator::extend() call instead of the User class directly. Not necessary, just a note for something to check out.
Good luck and don't be afraid to RTFC!
Instead of making your own validation rule that does validates two things (which you shouldn't really do, validate one thing at a time), you can use the unique rule and then make your own rule that validates the characters of the username.
For example:
$rules = ['username' => 'required|username|unique:users,username'];
Where the username rule is your custom rule that ensures it contains the correct characters.
Maybe a bit "dirty" but it works:
The controller validates the input with something
$rules = array(
'title' => 'no_collision:'.$input['project_id']
);
In the validator function flash the message to the Session before returning false:
//...
public function validateNoCollision($attribute, $value, $parameters)
{
$project = Project::find($parameters[0]);
if($value == $project->title){
Session::flash('colliding_message','This collides with '.$project->title($).' created by '.$project->user->name;
return false;
}else{
return true;
}
}
In the view do something like the following:
#if($errors->has('title'))
<span class="help-block">{{ Session::get('colliding_message') }}</span>
#endif

Resources