How to set a maximum per page in laravel dynamically? - laravel

I have a Products class that looks for these paginated items, but in the front end I allow the user to define how many items he wants to display per page (10, 30, 50, 100) the problem is that if someone passes 1000, the api returns 1000 records per page.
How can I validate this for all controllers and models dynamically?
I could do this "easily" by validating each request ('limit') on each controller, but it would not be practical, how can I do that?
public function index(Request $request)
{
$perPage = $request->input('limit'); // User input
$sort = 'global_performance';
$descending = 'desc';
$products = Product::where('status', 1)
->orderBy($sort, $descending)
->paginate($perPage); //
return $products;
}

You can validate the limit like this:
public function index(Request $request)
{
$this->validate($request, [
'limit' => ['required', 'integer', Rule::in([10, 30, 50, 100])]
]);
$perPage = $request->input('limit'); // User input
$sort = 'global_performance';
$descending = 'desc';
$products = Product::where('status', 1)
->orderBy($sort, $descending)
->paginate($perPage); //
return $products;
}
Now, add following line in just before controller class:
use Illuminate\Validation\Rule;
Update
More dynamic way might be creating custom request class like this:
Run following command to create a new form request class:
php artisan make:request PaginateRequest
This will create PaginateRequest class at App\Http\Requests directory like this:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PaginateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
Now change this class into following:
<?php
namespace App\Http\Requests;
use Illuminate\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;
class PaginateRequest 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 [
'limit' => ['required', 'integer', Rule::in([10, 30, 50, 100])]
];
}
}
After this, you can use in controller function by adding it as function parameter.
public function index(PaginateRequest $request)
{
$perPage = $request->input('limit'); // User input
$sort = 'global_performance';
$descending = 'desc';
$products = Product::where('status', 1)
->orderBy($sort, $descending)
->paginate($perPage); //
return $products;
}
Please don't forget to import it just before controller class like this:
use App\Http\Requests\PaginateRequest;
In this way, You can use this request class everywhere you need.
You can see more at documentation here: https://laravel.com/docs/5.8/validation

You could easily create a middleware. Apply it simply from kernel to each and every route or make a group in your route file to apply it to selective routes.
Inside the middleware just check the limit, if empty or more than the max limit you want let's say 100, make it 100 like so:
$limit = $request->input('limit');
if (empty($limit) || ($limit > 100)) {
$request['limit'] = 100;
}
wouldn't that work?
Here is link for middlewares in Laravel.

Related

Practical way to only return selected columns in Laravel in method

I am doubting (life of a junior developer) what would be the most practical way to only return the value from a specific column in Laravel of a database record.
Example controller method:
public function show(ProductsCategory $category)
{
return $category;
}
This outputs all the columns, like this
{"id":104,"category_name":"Soft drinks","created_at":"2021-06-09T17:16:54.000000Z","updated_at":"2021-06-09T17:16:54.000000Z"}
However what I am after is just getting the category_name column retuned, like this
{"category_name":"Soft drinks"}
I can accomplish this by doing
public function show($id)
{
$category = ProductsCategory::select('category_name')->findOrFail($id);
echo json_encode($category)
exit;
}
However doubting if this would be the most practical way to go? Is there an more elegant/straight forward way? Or am I grossly overthinking this?
I think its developer choice . One way to select column like you mentioned .Another way is like below
$category = ProductsCategory::findOrFail($id,['category_name']);
By default findOrFail($id, $columns = ['*']) return all columns so they mentioned *
Also instead of json_encode as json,you can directly return $category
Also if you want to pass custom headers or status code then you can return like below
return response()->json($category)
Here is json method params
/**
* Create a new JSON response instance.
*
* #param mixed $data
* #param int $status
* #param array $headers
* #param int $options
* #return \Illuminate\Http\JsonResponse
*/
public function json($data = [], $status = 200, array $headers = [], $options = 0);
If you want to get full control over returned resource you should use API Resources.
To create resource file run:
php artisan make:resource ProductsCategoryResource
In resource file you can define fields to return, i.e. if you need only id and name you do this:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductCategoryResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
}
Then, set response in controller:
public function show($id)
{
$category = ProductsCategory::select('id', 'name')->findOrFail($id);
return new ProductCategoryResource($category);
}
You can also return resource collection:
public function index()
{
$categories = ProductsCategory::select('id', 'name')->get()l
return ProductCategoryResource::collection($category);
}
I think this is really elegant and organized way.

Laravel unique field validation

I have a Product model with a text input field for the product number. In my Laravel application I validate this field to be unique to that specific user. So two users can have a same product number, but one user cannot have duplicate. So far the validation rules work when adding new products:
'product_no' => 'nullable|unique:products,product_no,NULL,id,user_id,' . auth()->user()->id
However, when editing the same product, the validation fails. Probably because it already exists. I am not sure how to exclude the existing ID in the validation. Any ideas?
Example as requested
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Request1 extends FormRequest
{
private $rules;
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
public function __construct()
{
parent::__construct();
$this->rules = [
'password' => [
'nullable',
]
];
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return $this->rules;
}
}
And the one with unique looks like this then
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Request2 extends Request1
{
public function __construct()
{
parent::__construct();
$this->rules[] = 'unique:products,product_no,NULL,id,user_id,' . auth()->user()->id';
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return $this->rules;
}
}

Laravel validation required_if with in:1

I need to use required_id with a in:1 as this field is required to be checked only if another field equal to 4
'affirm_agency' => 'required_if:role,4|in:1',
but when I write it that way I can't pass the validation when it doesn't equal to 1
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class userRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
public function rules()
{
return [ 'affirm_agency' => 'required_if:role,4|in:1',];
}
}
I typically handle something like this like so:
/**
* Validation rules
*
* #return array
*/
public function rules() : array
{
$rules = [
// add whatever default rules you always want
];
if (request()->role == 4) {
$rules['affirm_agency'] = 'required|in:1';
}
return $rules;
}
Please see laravel documentation
in:foo,bar,...
The field under validation must be included in the given list of values. Since this rule often requires you to implode an array, the Rule::in method may be used to fluently construct the rule:
use Illuminate\Validation\Rule;
Validator::make($data, [
'zones' => [
'required',
Rule::in(['first-zone', 'second-zone']),
],
]);
You need to update you authorize function as it is returning false now.
public function authorize()
{
return true;
}

L5 Using Hashid in middleware returns null

I am using hashid to hash the id parameters in url. I have it set up in my model to automatically hash the id. This is working fine. My problem is decoding the hash in a middleware returns null. I'm not sure if this is a problem with my middleware or because of the hashing.
Model:
public function getIdAttribute($value)
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
return $hashids->encode($value);
}
Middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class HashIdsDecode
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
dd($request->id); //Returns null on show method - example localhost:8000/invoices/afewRfshTl
if($request->has('id'))
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
dd($hashids->decode($request->input('id')));
}
return $next($request);
}
}
Route:
Route::resource('invoices','InvoiceController');
Controller:
public function show($id)
{
$invoice = Invoice::find($id);
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
NOTE: if I bypass the middleware and do it directly in my controller like this it works but it requires me to repeat myself over and over and probably not the best way to do it.
public function show($id)
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
$invoiceId = $hashids->decode($id)[0];
$invoice = Invoice::find($invoiceId);
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
Personally, I would be more inclined to write a model trait. You can then use the trait on only the models required, rather than assuming every ID argument in a request is a Hash ID.
E.g.
namespace App\Models\Traits;
use Hashids\Hashids;
use Illuminate\Database\Eloquent\Builder;
trait HashedId
{
public function scopeHashId(Builder $query, $id)
{
$hashIds = new Hashids(env('APP_KEY'), 10);
$id = $hashIds->decode($id)[0];
return $query->where('id', $id);
}
}
Then to use it, you'd use the trait on your Invoice model (edit):
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Invoice extends Model
{
use \App\Models\Traits\HashedId;
// ...
}
And execute the following query in your controller:
public function show($id)
{
$invoice = Invoice::hashId($id)->firstOrFail();
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}

Laravel 5 how to validate route parameters?

I want to validate the route parameters in the "form request" but don't know how to do it.
Below is the code sample, I am trying with:
Route
// controller Server
Route::group(['prefix' => 'server'], function(){
Route::get('checkToken/{token}',['as'=>'checkKey','uses'=> 'ServerController#checkToken']);
});
Controller
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests;
class ServerController extends Controller {
public function checkToken( \App\Http\Requests\CheckTokenServerRequest $request) // OT: - why I have to set full path to work??
{
$token = Token::where('token', '=', $request->token)->first();
$dt = new DateTime;
$token->executed_at = $dt->format('m-d-y H:i:s');
$token->save();
return response()->json(json_decode($token->json),200);
}
}
CheckTokenServerRequest
namespace App\Http\Requests;
use App\Http\Requests\Request;
class CheckTokenServerRequest extends Request {
//autorization
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'token' => ['required','exists:Tokens,token,executed_at,null']
];
}
}
But when I try to validate a simple url http://myurl/server/checkToken/222, I am getting the response: no " token " parameter set.
Is it possible to validate the parameters in a separate "Form request", Or I have to do all in a controller?
ps. Sorry for my bad English.
For Laravel < 5.5:
The way for this is overriding all() method for CheckTokenServerRequest like so:
public function all()
{
$data = parent::all();
$data['token'] = $this->route('token');
return $data;
}
EDIT
For Laravel >= 5.5:
Above solution works in Laravel < 5.5. If you want to use it in Laravel 5.5 or above, you should use:
public function all($keys = null)
{
$data = parent::all($keys);
$data['token'] = $this->route('token');
return $data;
}
instead.
Override the all() function on the Request object to automatically apply validation rules to the URL parameters
class SetEmailRequest
{
public function rules()
{
return [
'email' => 'required|email|max:40',
'id' => 'required|integer', // << url parameter
];
}
public function all()
{
$data = parent::all();
$data['id'] = $this->route('id');
return $data;
}
public function authorize()
{
return true;
}
}
Access the data normally from the controller like this, after injecting the request:
$setEmailRequest->email // request data
$setEmailRequest->id, // url data
If you dont want to specify each route param and just put all route params you can override like this:
Laravel < 5.5:
public function all()
{
return array_merge(parent::all(), $this->route()->parameters());
}
Laravel 5.5 or above:
public function all($keys = null)
{
// Add route parameters to validation data
return array_merge(parent::all(), $this->route()->parameters());
}
The form request validators are used for validating HTML form data that are sent to server via POST method. It is better that you do not use them for validating route parameters. route parameters are mostly used for retrieving data from data base so in order to ensure that your token route parameter is correct change this line of your code, from
$token = Token::where('token', '=', $request->token)->first();
to
$token = Token::where('token', '=', $request->input(token))->firstOrFail();
firstOrFail() is a very good function, it sends 404 to your user, if the user insert any invalid token.
you get no " token " parameter set because Laravel assumes that your "token" parameter is a POST data which in your case it is not.
if you insist on validating your "token" parameter, by form request validators you gonna slow down your application, because you perform two queries to your db,
one in here
$token = Token::where('token', '=', $request->token)->first();
and one in here
return [
'token' => ['required','exists:Tokens,token,executed_at,null']
];
I suggest to use firsOrFail to do both validating and retrieving at once.
A trait can cause this validation to be relatively automagic.
Trait
<?php
namespace App\Http\Requests;
/**
* Class RouteParameterValidation
* #package App\Http\Requests
*/
trait RouteParameterValidation{
/**
* #var bool
*/
private $captured_route_vars = false;
/**
* #return mixed
*/
public function all(){
return $this->capture_route_vars(parent::all());
}
/**
* #param $inputs
*
* #return mixed
*/
private function capture_route_vars($inputs){
if($this->captured_route_vars){
return $inputs;
}
$inputs += $this->route()->parameters();
$inputs = self::numbers($inputs);
$this->replace($inputs);
$this->captured_route_vars = true;
return $inputs;
}
/**
* #param $inputs
*
* #return mixed
*/
private static function numbers($inputs){
foreach($inputs as $k => $input){
if(is_numeric($input) and !is_infinite($inputs[$k] * 1)){
$inputs[$k] *= 1;
}
}
return $inputs;
}
}
Usage
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyCustomRequest extends FormRequest{
use RouteParameterValidation;
/**
* 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 [
//
'any_route_param' => 'required'//any rule(s) or custom rule(s)
];
}
}
For \App\Http\Requests\CheckTokenServerRequest you can add use App\Http\Requests\CheckTokenServerRequest; at the top.
If you pass the token by url you can use it likes a variable in controller.
public function checkToken($token) //same with the name in url
{
$_token = Token::where('token', '=', $token)->first();
$dt = new DateTime;
$_token->executed_at = $dt->format('m-d-y H:i:s');
$_token->save();
return response()->json(json_decode($token->json),200);
}
$request->merge(['id' => $id]);
...
$this->validate($request, $rules);
or
$request->merge(['param' => $this->route('param')]);
...
$this->validate($request, $rules);
You just missing the underscore before token. Replace with
_token
wherever you check it against the form generated by laravel.
public function rules()
{
return [
'_token' => ['required','exists:Tokens,token,executed_at,null']
];
FormRequest has a method validationData() that defines what data to use for validation. So just override that one with route parameters in your form request class:
/**
* Use route parameters for validation
* #return array
*/
protected function validationData()
{
return $this->route()->parameters();
}
or leave most of the all logic in place and override input method from trait \Illuminate\Http\Concerns\InteractsWithInput
/**
* Retrieve an input item from the request.
*
* #param string|null $key
* #param string|array|null $default
* #return string|array|null
*/
public function input($key = null, $default = null)
{
return data_get(
$this->getInputSource()->all() + $this->query->all() + $this->route()->parameters(), $key, $default
);
}

Resources