I am creating a logic where a controller can call another controller depend on Auth::user() role, but not all of controller shared same method, so i want if controller calling a method that does not exist it will throw a 404 not found.
here is my controller
class LokalController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
Public $controller;
public function __construct()
{
//$this->middleware('adminakses');
$this->middleware(function ($request, $next) {
$this->setController();
return $next($request);
});
}
public function setController()
{
$role = Auth::user()->role;
switch ($role)
{
case 'admin':
$this->controller = new \SIA\Http\Controllers\Admin\LokalController;
break;
case 'guru':
$this->controller = new \SIA\Http\Controllers\Guru\LokalController;
break;
case 'siswa':
$this->controller = new \SIA\Http\Controllers\Guru\LokalController;
break;
}
}
public function index()
{
return $this->controller->index();
}
for example Admin\LokalController has method A(), but Guru\LokalController doesn't, and if user logged in as guru and trying to cal method A() it should returning not found exception or something user understandable message, but currently showing BadMethodCallException method A() does not exist
The approach you're trying to work is achievable but I think it will be overwhelming in time.
public function index()
{
try {
$action = $this->controller . '#' . __FUNCTION__;
action($action);
} catch (Exception $e) {
abort('401', "Your Message");
}
}
How I'd do it instead is, I'd create a redirect path for the User model.
public function getRedirectPathAttribute() {
switch ($this->role) {
case 'admin':
return '/admin';
case 'guru':
return '/guru';
case 'siswa':
return '/siswa';
}
}
And then create a middleware to make the check, and if redirect is needed, I'd redirect using auth()->user()->redirect_path.
What do you think?
One option is to have a controller all this controllers inherits from that implement all methods by calling App::abort(404), and in each of the child classes implement the methods they need, stopping the methods in the parent classes from being called
class BaseController extends Controller{
public function a() {
App::abort(404);
}
}
class AdminController extends BaseController{
public function a() {
// to stuff for admin
}
}
Controllers inheriting from BaseController that don't implement a() will get 404, admin controller will not
Related
Route:
Route::controller(PublicController::class)->group(function () {
Route::get('/index', 'index')->name('public.index');
});
View:
index.blade.php
wrong_browser.blade.php
In controller, this way is ok:
class PublicController extends Controller
{
public function index(Request $request)
{
if(is_wrong_browser)
return view(public.wrong_browser);
return view('public.index');
}
}
But how can I return view from another function, like this, without making a new route:
class PublicController extends Controller
{
public function index(Request $request)
{
$this->CheckBrowser();
return view('public.index');
}
public function CheckBrowser()
{
if(is_wrong_browser)
return view(public.wrong_browser);
}
}
You can use the method redirect.
return redirect()->route('index');
You could use middleware which you either define globally, or on specific routes.
class CheckUserActive
{
public function handle($request, Closure $next)
{
// determine value of $is_wrong_browser
$is_wrong_browser = true;
if ($is_wrong_browser) {
return redirect()->route('is-wrong-browser-route');
}
return $next($request);
}
}
It is bad practice to return a view from middleware instead redirect your user to another route.
Alternatively, you could have a base Controller that your Controllers extend which has the checkBrowser function defined on it and the extending Controllers therefore have access to:
class WrongBrowserController extends \App\Http\Controllers\Controller
{
public function checkBrowser()
{
// determine value of $is_wrong_browser
$is_wrong_browser = true;
if ($is_wrong_browser)
{
return view('wrong-browser-view');
}
}
}
class PublicController extends WrongBrowserController
{
public function index(Request $request)
{
$this->checkBrowser();
return view('index');
}
}
I created one policy inside create function i am checking weather this user can able to create records and i am registering the Model and policy in the AthServiceProvider.php after that i am checking inside the controller by using $this->authorize('create') it's failing always even the user is valid,can you please help me how to resolve this issue
Error:- This Action is unathorized
restaurentContoller.php
class RestaurentsController extends Controller
{
protected $repository;
public function __construct(RestaurentRepository $repository){
$this->repository = $repository;
}
public function postRestaurent(RestaurentRequest $request){
$data = $request->all();
$data['admin_id'] = $this->getAccountId($request);
$this->authorize('create');
$rest = $this->repository->create($data);
return response()->json(fractal($rest,new RestuarentTransformer));
}
}
RestaurentPolicy.php
public function create(User $user)
{
return ($user->admin_id=1) ? true : false;
}
api.php
Route::post('/postRest',[RestaurentsController::class,'postRestaurent'])->middleware(['CheckAdmin']);
If you use Request Classes you have to change authorize method return false to true
class RestaurentStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return false;
}
}
I have a resource controller and want to add an extra custom policy method for destroyMany
In which I would check if the user is admin before deleting many.
The default methods work fine
Controller Method
Policy Method
index
viewAny
show
view
create
create
store
create
edit
update
update
update
destroy
delete
destroyMany
destroyMany
Controller destroyMany method is called, the policy isn't
Or should I stick to Gates for this extra method?
The docs say I can have any name for the methods and policies, How can both be linked?
destroyMany->destroyMany or
destroyMany->deleteMany would be a good setup.
And would be a great addition to my resource controller (where it should reside)
class ResourceController extends Controller
{
public function __construct()
{
$this->middleware('auth:api');
$this->authorizeResource(Resource::class, 'resource');
}
public function index()
{
return ResourceCollection::collection(Resource::all());
}
public function destroyMany(Request $request)
{
// gets called but needs a policy which isn't called
}
}
policy
class ResourcePolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function viewAny(User $user)
{
// works
return $user->hasAnyRoles(['admin', 'superAdmin']);
}
public function delete(User $user, Resource $resource)
{
// works
return $user->hasAnyRoles(['admin', 'superAdmin']);
}
public function deleteMany(User $user, Resource $resource)
{
// not called because the controller method needs to be hooked up, like the other methods
}
}
To get the addition policy method to work you will need to update the resourceAbilityMap for the controller. Adding the following to your controller should do the trick:
protected function resourceAbilityMap()
{
return array_merge(parent::resourceAbilityMap(), [
'destroyMany' => 'deleteMany'
]);
}
Also, if you don't return anything from your deleteMany policy method it will result in a 403.
If you're route/controller method isn't receiving an instance of the model then you will also need to update the array returned from the resourceMethodsWithoutModels method:
protected function resourceMethodsWithoutModels()
{
return array_merge(parent::resourceMethodsWithoutModels(), ['destroyMany']);
}
I'm trying to refactor my code to be more reusable.
I created a trait CrudControllerTrait to implement the index,show,store,update,destroy methods.
But I found 2 problems:
BrandController.php
public function store(BrandNewRequest $request)
{
$requestData = $request->validated();
return new BrandResource($this->brands->store($requestData));
}
ProductController.php
public function store(ProductNewRequest $request)
{
$requestData = $request->validated();
return new ProductResource($this->products->store($requestData));
}
The trait method would be:
public function store(xxxxx $request)
{
$requestData = $request->validated();
return new xxxxxResource($this->repository()->store($requestData));
}
Problem1: The hint type. How can I abstract them? If I remove it shows that errror:
"message": "Too few arguments to function App\\Http\\Controllers\\BrandController::store(), 0 passed and exactly 1 expected"
Problem2: Return the resource. How can create the new resource? On the collection I can solve it doing this:
public function index()
{
$models = $this->repository()->index();
return $this->resource()::collection($models);
}
The resource is on the controller who uses the trait:
public function resource()
{
return BrandResource::class;
}
But with single resource didn't know how to do it...
The idea is, that I have so much controllers using the same pattern: BrandController, ProductController, etc. I'd love to reuse these 5 crud methods on the same trait...
The only way I found is creating an abstract method.
trait CrudRepositoryTrait
{
abstract function model();
public function index()
{
return $this->model()::with($this->with())->get();
}
public function find($id)
{
return $this->model()::findOrFail($id);
}
public function store($data)
{
$request = $this->dtoRequest($data);
return $this->model()::create($request);
}
(...)
}
And then, an example how to use this treat:
class ProductRepository implements ProductRepositoryContract
{
use CrudRepositoryTrait;
function model()
{
return Product::class;
}
(...)
}
By this way I could reuse a lot of code.
Bindings
I'm using bindings in my service provider between interface and implementation:
public function register()
{
$this->app->bind('MyInterface', MyImplementation::class);
}
Middleware
In my middleware, I add an attribute to the request:
public function handle($request, Closure $next)
{
$request->attributes->add(['foo' => 'bar]);
return $next($request);
}
Now, I want to access foo in my service provider
public function register()
{
$this->app->bind('MyInterface', new MyImplementation($this->request->attributes->get('foo')); // Request is not available
}
The register() is called before applying the middleware. I know.
I'm looking for a technique to 'rebind' if the request->attributes->get('foo') is set
Try like this:
public function register()
{
$this->app->bind('MyInterface', function () {
$request = app(\Illuminate\Http\Request::class);
return app(MyImplementation::class, [$request->foo]);
}
}
Binding elements works like this that they will be triggered only when they are call.
In service provider You can also access Request Object by:
public function register()
{
$request = $this->app->request;
}
The accepted answer is good, however it does not address the issues regarding DI. So in your Service Provider you need:
public function register()
{
$this->app->bind('MyInterface', function () {
return new MyImplementation(request()->foo);
}
}
But you need to be careful with DI. If you do this in your Controller:
class MyController extends Controller
{
public function __construct(MyInterface $myInterface)
{
$this->myInterface = $myInterface;
}
}
It will NOT work! The constructor of the controller is called BEFORE the group middleware is applied, so the foo parameter will be null on MyImplementation.
If you want to use DI, you need to either resolve it using App::make(MyInterface::class) outside of the constructor, or even better pass your dependency in the Controller's method:
class MyController extends Controller
{
public function index(MyInterface $myInterface)
{
$myInterface->getFoo();
}
}
Above will work because the controller's method is executed after the middlewares are applied.
This is the flow of a laravel request:
Global middleware run
Target controller's __construct run
Group middleware run
Target controller's method/action run (in above case index)
Try this
public function register()
{
$this->app->bind('MyInterface', function ($app) {
return new MyImplementation(request()->foo);
}
}