I want to create an object in my middleware (in this case, a collection from an Eloquent query), and then add it to the IOC container so I can type-hint method signatures in my controllers to access it.
Is this possible? I can't find any examples online.
You can do that very easy, in several steps.
Create new middleware (name it like you want)
php artisan make:middleware UserCollectionMiddleware
Create new collection class that will extend Eloquent database collection. This step is not required, but will let you in future to create different bindings, using different collection types. Otherwise you can do only one binding to Illuminate\Database\Eloquent\Collection.
app/Collection/UserCollection.php
<?php namespace App\Collection;
use Illuminate\Database\Eloquent\Collection;
class UserCollection extends Collection {
}
Add your bindings in app/Http/Middleware/UserCollectionMiddleware.php
<?php namespace App\Http\Middleware;
use Closure;
use App\User;
use App\Collection\UserCollection;
class UserCollectionMiddleware {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
app()->bind('App\Collection\UserCollection', function() {
// Our controllers will expect instance of UserCollection
// so just retrieve the records from database and pass them
// to new UserCollection object, which simply extends the Collection
return new UserCollection(User::all()->toArray());
});
return $next($request);
}
}
Don't forget to put the middleware on the desired routes, otherwise you will get an error
Route::get('home', [
'middleware' => 'App\Http\Middleware\UserCollectionMiddleware',
'uses' => 'HomeController#index'
]);
Now you can type hint this dependency in your controller like this
<?php namespace App\Http\Controllers;
use App\Collection\UserCollection;
class HomeController extends Controller {
/**
* Show the application dashboard to the user.
*
* #return Response
*/
public function index(UserCollection $users)
{
return view('home', compact('users'));
}
}
Related
I would like to share some variables from session in all views in Laravel 8. According to documentation I call View::share() method in AppServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
//
}
public function boot()
{
// Variables shared in all views.
View::share('showModal', session('showModal'));
}
}
The problem is that although the session showModal key is really set, I can't get it in AppServiceProvider::boot(). If I call session('showModal') in the controller on in view I can see the correct value. Only AppServieProvider returns null.
Can somebody explain please what is wrong with this code?
Well, alternatively, you could set up a middleware to be responsible for updating your session variable before the call in view.
Below is something I implemented for setting/retrieving the user permissions before the call in view.
STEP 1
Generate the middleware: php artisan make:middleware GetPermissions
You will find the middleware in App\Http\Middleware directory.
STEP 2
Add your logic for setting your session variable in the new middleware's handle method.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class GetPermissions
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$request->session()->put("permissions", optional(auth()->user(), function ($user) {
return $user->permissions()->pluck("name")->toArray();
}) ?? []);
return $next($request);
}
}
STEP 3
Register your middleware in the protected $middlewareGroups array of App\Http\Kernel.php class file.
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
// ...
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\GetPermissions::class,
],
// ...
}
With that setup, every time a request is made, the latest values of your session variable will be set.
You will then have access to latest session variable value in your helperfiles, controllers as whereas views.
Demo for accessing it in views:
{{session("permissions")}}
We're using a microservice architecture. There are 2 sets of data for the laravel service:
The database that houses the admins.
And all the other data that admins can access, which comes via GRPC
calls to other services.
I want something like eloquent (maybe API Resource?) for structuring data/relationships, but instead of making Database-queries to load data, it needs to make GRPC calls to other services. I was thinking of making a custom class that extends off of eloquent and overloading the protected functions that make calls to the database, but that sounds like a recipe for a bad time. If anybody has experience doing what I'm describing, what direction did you go? What worked? what didn't?
So, I ended up not using eloquent at all. I continued using the protoc set up as the documentation explains. But I used Route-binding to enable type-hinting in controllers:
<?php
namespace App\Providers;
use OurNamespace\GrpcClient;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use OurNamespace\Customer;
use OurNamespace\CustomerIdInput;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* #var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
//
parent::boot();
Route::bind('customer', function ($customerId) {
$grpcClient = app(GrpcClient::class);
$customerIdInput = new CustomerIdInput();
$customerIdInput->setCustomerId($customerId);
list($customer, $status) = $grpcClient->GetCustomerDetails($customerIdInput)->wait();
if ($status->code != 0) {
error_log('ERROR - ' . print_r($status, 1));
return redirect()->back()->withErrors(['There was an error retrieving that customer', $status->details]);
}
return $customer;
});
The GrpcClient is coming from the AppServiceProvider. This way if we want to make a query we don't have to manually instantiate it.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use OurNamespace\GrpcClient;
use Grpc\ChannelCredentials;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->singleton('OurNamespace\GrpcClient', function ($app) {
return new GrpcClient(env('GRPC_HOST'), [
'credentials' => ChannelCredentials::createInsecure(),
]);
});
In my inherited code in the Models there's some serious logic and I want to use the Laravel's Dependency Injection in order to load the models as Dependencies into the controller instead of Using the Laravel's provided Facades.
So here's a sample Controller:
namespace App\Http\Controllers;
use App\User;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*
* #param int $id
* #return View
*/
public function show($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
}
But Instead of using the Facade User I want to be able to load it as dependency into the controller:
namespace App\Http\Controllers;
use App\User;
use App\Http\Controllers\Controller;
user App\Models\User
class UserController extends Controller
{
/**
* #var User
*/
private $user=null;
public function __construct(User $user)
{
$this->user=$user;
}
/**
* Show the profile for the given user.
*
* #param int $id
* #return View
*/
public function show($id)
{
return view('user.profile', ['user' => $this->user->findOrFail($id)]);
}
}
The reason why I want to do that is because I come from Symfony Background where the Dependency Injection Pattern Is heavily Erdosed. Also Dependency Injection is the Unit Test's best buddy, so I want to be able to unitilize the Dependency Injection that I am familiar with.
So I wanted to know whether I can inject the models where the logic exists in the Controllers Instead of using the Facade Pattern provided by laravel.
When you register your route, you can use the model binding:
// routes/web.php
Route::get('users/{user}', 'UserController#show');
Then in your controller, you can change your method to:
public function show(User $user)
{
//
}
Where the $user will be the instance of App\User with the right id. For example, if the url is /users/1, the $user will contain the App\User with id 1.
For more information: https://laravel.com/docs/5.8/routing#route-model-binding
I am trying to create multiple Auth Guards and services for my API. I want specific group to be available to specific users (more like RBAC without sessions).
If a user tries to access a group that has admin:auth as middleware its privileges will be checked. If it has api:auth then no privilege check.
I can't understand how to do this. I have added the following lines in the bootstrap/app.php
$app->routeMiddleware([
'admin' => App\Http\Middleware\Admin::class,
'api' => App\Http\Middleware\Api::class,
]);
And:
$app->register(App\Providers\AdminServiceProvider::class);
$app->register(App\Providers\ApiServiceProvider::class);
and created the Files Admin.php and APi.php in Middleware folder with the following content (basically same as Authenticate.php with name changes)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class Admin
{
/**
* The authentication guard factory instance.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
return response('Unauthorized.', 401);
}
return $next($request);
}
}
And the AdminServiceProvider and ApiServiceProvider in the App\Providers folder with just this in function boot():
var_dump($this->app['api']);exit;
And this on top:
namespace App\Providers;
use App\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class ApiServiceProvider extends ServiceProvider
But I get the Following Error:
What am I missing? I have already done composer dump-autoload, no difference.
Regards
Well what I ended up using is more like a work around, but I think this might be how it is supposed to be used.
So what I did was that I created two groups (a parent and a child) with the middlewares I needed as follows:
$app->group(["middleware" => "middleware1"], function() use ($app){
$app->group(["middleware" => "middleware1"], function() use ($app){
$app->post("/signin", "AppController#signin");
}
}
This way, the signin route is reached after going through 2 middlewares.
Regards
I am a newbie in Laravel framework and I want to extend a base controller which in turn extends controller. However, I discovered that when I do that, my controller no longer recognises my session variables.
Here is my code
namespace App\Http\Controllers\Settings;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Auth\PermissionController;
use App\Fee;
class FeeController extends PermissionController
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
dd(session('userdata')['user_urls']);
$data['title']="Fees";
$data['fees']=Fee::all();
return view('settings.fee.index',$data);
}
And this is my PermissionController code
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PermissionController extends Controller {
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct(Request $request) {
if(!session('userdata')['user_urls']->contains($request->path())){
dd(session('userdata')['user_urls']);
}
}
}
But I realize that my session('userdata')['user_urls'] becomes null at the PermissionController. But if I make FeeController to extend Controller, my session variables are intact.
I need to use the session variables for some control at the permission controller.
I am running Laravel 5.3 on a MAC OSX and PHP 7
I have figured out the problem. Actually, PermissionController is not registered in the web middleware group so that session is not persisting in the PermissionController. So the solution to your question is just make a trait named as Permission instead of the controller and use it in FeesContorller.
trait Permission{
public function permission(Request $request) {
if($request->session()->get('name') != null){
echo "Hello World";
}
}
}
And FeesController like this:
class FeesController extends Controller
{
use Permission;
public function index(Request $request)
{
$this->permission($request); // the method of the trait.
echo "\n".$request->session()->get('name');
}
}
Output:
If the name attribute is set in session then :
Hello World
Passion Infinite
Otherwise
No Output // null
I have solved the same problem with middleware. I have created a middleware that takes care of the authorization of requests by checking the session to ensure that the controller action being accessed is available in session.
This is the middleware
namespace App\Http\Middleware;
use Closure;
class PermissionMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$currentAction = \Route::currentRouteAction();
list($nothing,$route_action) = explode('App\Http\Controllers\\', $currentAction);
$user_actions=session('userdata')['user_urls'];
if((empty($user_actions))||!$user_actions->contains($route_action)){
return redirect('denied');
}
return $next($request);
}
}
This is the controller
namespace App\Http\Controllers\Settings;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Auth\PermissionController;
use App\Fee;
class FeeController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$data['title']="Fees";
$data['fees']=Fee::all();
return view('settings.fee.index',$data);
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create(Request $request)
{
$data['title']='New Fee';
return view('settings.fee.create',$data);
}
So, instead of using the routes (cos of some other reasons), I used the controller actions.
So, once a user logs in, all the controller actions he can access are loaded into session. When he tries to perform any action, the middleware, does the check to ensure he is allowed to perform that action. Otherwise, he is routed away.
So, I either add 'permission' to the routes middleware or call
$this->middleware('permission')
on the controller's construct method.
That is working for me now.
Thank you everybody for your contributions.