I am new to Laravel and would like to know the best pracitse method to handle duplicate code.
I have nearly in all functions of my api-controllers this code at beginning:
// Validate parameters
$validator = Validator::make($request->all(), $this->validationRules);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
So normally I would put it in some function. But this code also used in lot of other controllers. so this function should be called also by other controllers.
What is best practise to handle that in Laravel 5? My first idea was to make some own Controller with this function and my other controllers would extend this.
Like:
class MyController extends Controller
{
protected function handleFailByPrameter($params)
{
....
}
}
class Api1Controller extends MyController
{
public function apicall1()
{
$this->handleFailByPrameter($this->validationRules());
}
}
But maybe there are some other methods to handle that kind of things?
You may create a validation function in your helper this will help you do that and then call this new function from anywhere in your laravel app(literally anywhere). Second thing you can do is create custom requests which will validate your data before it is passed to your function in the following way :
first generate a new requests class from artisan
php artisan make:request ExplicitRequestName
this will create a new file named ExplicitRequestName.php under app/Http/Requests folder open it, toggle false to true in your authorize function and then define your validation rules in following manner:
public function rules()
{
return [
'email' => 'required',
'password' => 'required',
];
}
call this requests object in your function :
public function someFunction(Requests\ExplicitRequestName $request)
{
}
Laravel 5.1 has a method attached to the Controller:
$this->validate($request, [your rules]);
It will redirect back with errors if anythings wrong, or send an error response if it's ajax.
To not defined your rules in every method, you can set in your models:
public static $rules = ['Your rules']
Then you can simply do your validation in your controllers, forexample:
$this->validate($request, Post::rules);
I think you are already on the right track. I implemented the validation and authentication for every API this way, although on laravel 4
.
Related
I need to make my own validator that extends Illuminate\Validation\Validator
I have read an example given in an answer here: Custom validation in Laravel 4
But the problem is it doesn't clearly show how to use the custom validator. It doesn't call the custom validator explicitly. Could you give me an example how to call the custom validator.
After Laravel 5.5 you can create you own Custom Validation Rule object.
In order to create the new rule, just run the artisan command:
php artisan make:rule GreaterThanTen
laravel will place the new rule class in the app/Rules directory
An example of a custom object validation rule might look something like:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class GreaterThanTen implements Rule
{
// Should return true or false depending on whether the attribute value is valid or not.
public function passes($attribute, $value)
{
return $value > 10;
}
// This method should return the validation error message that should be used when validation fails
public function message()
{
return 'The :attribute must be greater than 10.';
}
}
With the custom rule defined, you might use it in your controller validation like so:
public function store(Request $request)
{
$request->validate([
'age' => ['required', new GreaterThanTen],
]);
}
This way is much better than the old way of create Closures on the AppServiceProvider Class
I don't know if this is what you want but to set customs rules you must first you need to extend the custom rule.
Validator::extend('custom_rule_name',function($attribute, $value, $parameters){
//code that would validate
//attribute its the field under validation
//values its the value of the field
//parameters its the value that it will validate againts
});
Then add the rule to your validation rules
$rules = array(
'field_1' => 'custom_rule_name:parameter'
);
I am using Laravel 5.1. My controller is specifically for admin users. So I check whether user is admin or not.This is my code.
public function getAdminData()
{
$this->checkAdminStatus();
return response()->json(array('admin-data'));
}
public function checkAdminStatus()
{
$userManager = new UserManager();
if(!$userManager->isAdmin())
{
return redirect()->route('returnForbiddenAccess');
}
}
My route is
Route::any('api/app/forbidden',['uses' =>'ErrorController#returnNonAdminErrorStatus','as'=>'returnForbiddenAccess']);
Now if user is not admin, then it should not return admin-data yet it returns. Shouldn't it stop processing logic after redirect()->route call?
Also this is purely REST application.
Why don't you use Laravel Middleware solution for your need ? You can link a middleware to your controller, checking if the current user is an administrator, and redirect if not :
//You Middleware Handle method
public function handle($request, Closure $next)
{
if ($this->auth->guest() || !($this->auth->user()->isAdmin))
{
return redirect('your/url')->with('error','no admin');;
}
return $next($request);
}
You can add on or multiple middleware for a controller in his construct method
//your controller
public function __construct(Guard $auth, Request $request){
$this->middleware('auth', ['except' => ['index', 'show']]); //here one 'auth' middleware
$this->middleware('admin', ['only' => ['index', 'show', 'create','store']]); //here the admin middleware
}
Notice the onlyand except parameters that allow or disallow the middleware for some controller methods
Check laravel documentation on Middleware for more information :)
No, your logic is slightly flawed. The return value you are sending back from checkAdminStatus() is simply being ignored and thrown away:
public function getAdminData()
{
// You don't have $redirectValue in your code, but imagine it
// is there. To actually redirect, you need to return this value
// from your controller method
$redirectValue = $this->checkAdminStatus();
Nothing is being done with that redirect. The only time something is being returned from your controller is happening on every single request. I would suggest something more like this:
public function getAdminData(UserManager $userManager)
{
if($userManager->isAdmin()) {
return response()->json(array('admin-data'));
}
return redirect()->route('forbidden-access');
}
I think this captures the spirit of your question: the only time anything is being returned here is if the user is an admin.
As an aside, you are returning JSON data in one case, and a redirect in another. This may not be a very good idea. My reasoning is because, normally, JSON data is returned in response to AJAX requests, which in my experience are seldom followed up with an actual redirect in the event of failure. (YMMV)
In Laravel 5.1 I need to pass some data to the register view.
The getRegister() function in RegistersUsers trait is responsible to return the view.
At first I modified the function to pass my data but then I realized that the modifications would be overridden in case of an update.
So I made a new controller registerController and modified the route for getRegister like this: Route::get('auth/register', 'Auth\RegisterController#getRegister')
Inside the controller I redefined the getRegister function to return the view with my additional data.
Now I am thinking.. am I doing this correctly or do I need to use some other method and use the original AuthController some other way?
Also, default auth is set to use email for post login, how do I change it to use username without touching the foundation files?
Are all these matters regarding "extending the framework" ?
Thanks
First of all, it's always a bad idea to modify vendor files as the changes would be overwritten in case of any update in vendor package.
Answering your first question:
If you want to provide some additional data in registration view, you could do 2 things.
First one is to add your own getRegister() method:
public function getRegister()
{
return view('auth.register')->with(<parameter name>, <parameter value>);
}
The drawback of this solution is that in case of any future changes in trait's getRegister method those changes will not be incorporated into your controller.
So the better approach is to reuse trait's getRegister() method in your controller and add your parameters to whatever trait returns:
In your controller do:
use RegistersUsers {
RegistersUsers::getRegister as traitGetRegister;
}
public function getRegister()
{
return $this->traitGetRegister()->with(<parameter_name>, <parameter_value>);
}
Answering your second question:
Laravel's Auth::attempt() method that is used to login users uses DatabaseUserProvider to load users from the DB and that provider is flexible enough to use any credential set you provide. However, if you want to use AuthenticatesUsers trait to login users, you have to override its postLogin method, because of the validation it does for user credentials:
$this->validate($request, [
'email' => 'required|email', 'password' => 'required',
]);
UPDATE FOR LARAVEL 5.1: Since 5.1 there is no need to override postLogin() to change the column that is used to fetch users from the database. It is enough to set username property in the controller to the name of the column:
public $username = 'login';
I am learning Symfony2 Framework, and to start things of I found this tutorial: Tutorial. Everything has been going well until I hit this issue:
in Tutorial Part2. In Creating the form in the controller section i discover that the getRequest() function is deprecated and bindRequest() is not found in class.
These two have held me back with the tutorial and learning progress. I there any other way of constructing this controller without using these functions or are there other functions that do exactly the same thing.
See this part of the Symfony Documentation. It shows that you should use handleRequest, like so:
// Top of page:
use Symfony\Component\HttpFoundation\Request;
...
// controller action
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
// ...
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
// perform some action...
return $this->redirect($this->generateUrl('task_success'));
}
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
You may also find this link useful: Handling Form Submissions.
The Request as a Controller Argument
Getting the request object in this way might be a little confusing at first, to quote the documentation:
What if you need to read query parameters, grab a request header or
get access to an uploaded file? All of that information is stored in
Symfony's Request object. To get it in your controller, just add it as
an argument and type-hint it with the Request class:
use Symfony\Component\HttpFoundation\Request;
public function indexAction($firstName, $lastName, Request $request)
{
$page = $request->query->get('page', 1);
// ...
}
This information can be found on the Controller Documentation.
I try to set some specific filter on all controller methods with:
public function __construct() {
$this->beforeFilter(function(){
//whathever
});
}
and it's working well on normal GET methods, problem occures when there is some POST method:
Route::post('settings/menu-order/{direction}', array(
'as' => 'setting.menu-order.move',
'uses' => function($direction) {
$controller = new CMSSettingsController();
return $controller->doMoveMenu($direction);
}));
after click in a button which send POST with $direction, I'v got
Call to a member function filter() on a non-object
in vendor/laravel/framework/src/Illuminate/Routing/Controller.php
protected function registerClosureFilter(Closure $filter)
{
$this->getFilterer()->filter($name = spl_object_hash($filter), $filter);
return $name;
}
If I use already registred filter it's working, so what's going on?
I have few controllers which need specific function todo before running controller methods, so I can't make global universal filter. Is there any other good solution?
The problem could be that you are calling the controller action directly instead of letting the Router do it for you. When the router tries to apply the filters, instead of applying them on the controller, it ends up attempting to apply them on the output of the doMoveMenu action - which, of course, is not a Controller object and has no method filter.
Instead, your route should look like this:
Route::post('settings/menu-order/{direction}', array(
'as' => 'setting.menu-order.move',
'uses' => 'CMSSettingsController#doMoveMenu'));
The reason you don't need to do the method call manually is that since your Route has a parameter in it and your method accepts a parameter, the Router will automatically pass the parameter into the action method. Additionally, since you are providing a method name as the uses value, Laravel knows that it has to instantiate the Controller and run the filters.