any advice how to trigger a controller method with a cronjob?
I've created an artisan command
public function schedule(Schedulable $scheduler)
{
return $scheduler->everyMinutes(1);
}
/**
* Execute the console command.
*
* #return mixed
*/
public function fire()
{
if (Auth::check())
{
$users = Auth::user();
Log::info($users);
}
}
Instead of logging I want to call a Controller Method. Is this possible?
The fact that you're trying to call a controller method from elsewhere is indicative that your controller has too much responsibility. Ideally you should move the functionality elsewhere such that both the controller and the job can access it.
However, if you don't want to do that you could create a request and use the router to dispatch it internally.
$request = Request::create('uri/of/controller', 'GET', $params);
return Route::dispatch($request)->getContent();
You may also be able to use the Container to get an instance of the controller and call the method directly.
App::make('YourController')->yourMethod($params);
Better practice is to create trait or helper and use it both inside you controller and artisan commands.
Related
I am trying to use Laravel authorization policies with API and Sanctum. However, I use middleware on the route as follows.
Route::get('/user/orders/{order}',
[OrderController::class, 'get_user_order_detail'])
->middleware('can:view:order');
OrderPolicy.php
namespace App\Policies;
use App\Models\Order;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class OrderPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
// dd(1);
}
public function view(User $user, Order $order): bool
{
return $user->id === $order->user_id;
}
}
As you see, when I add dd(1) inside the constructor of the OrderPolicy, then I get 1 as expected, but when I move it to the inside of view function, I get unauthorized which indicates that is maybe the view function itself is not being called, but, the OrderPolicy is getting called.
Your middleware definition is wrong:
->middleware('can:view:order')
it should be:
->middleware('can:view,order')
From the docs:
Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the Illuminate\Auth\Middleware\Authorize middleware is assigned the can key in your App\Http\Kernel class. Let's explore an example of using the can middleware to authorize that a user can update a post:
use App\Models\Post;
Route::put('/post/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');
In this example, we're passing the can middleware two arguments. The
first is the name of the action we wish to authorize and the second is
the route parameter we wish to pass to the policy method. In this
case, since we are using implicit model binding, a App\Models\Post
model will be passed to the policy method. If the user is not
authorized to perform the given action, an HTTP response with a 403
status code will be returned by the middleware.
I have a “security” service which I want to gradually move over to a Laravel Gate, so I can benefit from the helper methods that Laravel provides within the rest of the APP.
I defined the gate as follows now:
Gate::define('denja', function($user, $module, $permission) {
// validation of access to $module and $permission goes here
});
This works fine when I do
$user->can('denja', ['accounting', 'invoice.create']);```
for instance, but I don’t see how in my routes, I can define the middleware to properly function...
Route::post( '/accounting/invoices', 'InvoiceController#create')
->middleware("can:denja,accounting,invoice.create");```
Passing these parameters seems to be impossible from the middleware - the page now always returns a 403...
Any thoughts on how I can pass these parameters correctly to the gate from the Middleware? I think it's in fact a problem with the parameters; even with a dd() in the defined gate, I'm getting the 403.
I know I’m a bit “abusing” the system, but since we have an existing service that basically expects a user, module and permission under that module, I just want to delegate to that service for now...
When you are using can middleware :
The first is the name of the action we wish to authorise and the later is the route parameter we wish to pass to the policy method or a Model class path. documentation
For example :
Route::put('/post/{postId}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,postId');
OR
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
In your case :
Route::post( '/accounting/invoices', 'InvoiceController#create')
->middleware("can:denja,accounting,invoice.create");
which is missing the basic parameter signatures as there is no route param with name accounting or invoice.create nor a class.
Solution :
Remove middleware from route declaration :
Route::post( '/accounting/invoices', 'InvoiceController#create');
You can use can() method in your controller :
public function create(Request $request){
// Initialize $model and $permissions
// as per your business logic
if(!$request->user()->can('denja', $module, $permission){
abort(403);
}
// continue your logic for authorised user
}
Even if above solution works, if you have more authorisation rules, its better to make a policy class.
I to had this same problem so I did some digging into the 'can' middleware (Which maps to Illuminate\Auth\Middleware\Authorize)
Once in the class we see the following code
/**
* Get the model to authorize.
*
* #param \Illuminate\Http\Request $request
* #param string $model
* #return \Illuminate\Database\Eloquent\Model|string
*/
protected function getModel($request, $model)
{
if ($this->isClassName($model)) {
return trim($model);
} else {
return $request->route($model, null) ?:
((preg_match("/^['\"](.*)['\"]$/", trim($model), $matches)) ? $matches[1] : null);
}
}
What this means is...
If our string passed in is a class name then return that class name
If it is not a class name then...
1) Try to get it from the route, then return the route param
2) Try to get the model from the string via the regex "/^['\"](.*)['\"]$/"
So now lets say we have the middleware call of
$this->middleware(sprintf("can:create,%s,%s", User::class, Role::SUPPORT));
This will not work because the Role::SUPPORT does not match the regex
To match it we simply need to place the Role::SUPPORT into quotes.
TAKE NOTE OF THE "'" around the second %s
$this->middleware(sprintf("can:create,%s,'%s'", User::class, Role::SUPPORT));
To answer your question specifically, quote your string
Route::post('/accounting/invoices', 'InvoiceController#create')
->middleware("can:'denja','accounting','invoice.create'");
In one of my applications I have a property that is needed throughout the app.
Multiple different parts of the application need access such as requests, local and global scopes but also commands.
I would like to "cache" this property for the duration of a request.
My current solution in my Game class looks like this:
/**
* Get current game set in the .env file.
* #return Game
*/
public static function current()
{
return Cache::remember('current_game', 1, function () {
static $game = null;
$id = config('app.current_game_id');
if ($game === null || $game->id !== $id) {
$game = Game::find($id);
}
return $game;
});
}
I can successfully call this using Game::current() but this solutions feels "hacky" and it will stay cached over the course of multiple requests.
I tried placing a property on the current request object but this won't be usable for the commands and seems inaccessible in the blade views and the objects (without passing the $request variable.
Another example of its usage is described below:
class Job extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('game_scope', function (Builder $builder) {
$builder->whereHas('post', function ($query) {
$query->where('game_id', Game::current()->id);
});
});
}
}
I do not believe I could easily access a request property in this boot method.
Another idea of mine would be to store the variable on a Game Facade but I failed to find any documentation on this practice.
Could you help me find a method of "caching" the Game::current() property accessible in most if not all of these cases without using a "hacky" method.
Use the global session helper like this:
// Retrieve a piece of data from the session...
$value = session('key');
// Store a piece of data in the session...
session(['key' => 'value']);
For configuration info and more options: https://laravel.com/docs/5.7/session
I have a system in laravel 5.3 that uses over 40 controllers and probably 200 views.
I am attempting to clean up the code and use best practice. Given that certain calls are made pretty much everywhere, it makes sense to define it somewhere "semi globally". I would assume this would be in the Controller from which all controllers extend.
One object is $user, and has child $user->organisations and $user->organisation->locations.
Loading this at a base controller (or equivalent) way would also give me the advantage that I could ensure child relationships were eager loaded in an optimal way ensuring any foreach style code never results in multiple small database lookups. There are a number of other items I want to do this for with similar ramifications for database optimisation. These all use the Auth::user(), and they affect permissions with child objects.
Given that there are about 20 properties/variables of use to be shared (all dependant on Auth::user()) removing this duplicated code from almost every method is a huge improvement.
My aim is to be able to reference $this->user from any controller, and already have pre-loaded all the child/related objects.
Laravel 5.3 re-organised the loading order, so sharing the logged-in user data as part of Controller::__construct is no longer feasible.
Here's the code attempted so far:
In Controller::__construct
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
view()->share('user', $this->user);
return $next($request);
});
Unsurprisingly, this correctly sets the $user variable at View level, but not Controller level. While I do $user it at view level, this doesn't help.
Given that there are about 20 properties/variables of use to be shared (all dependant on Auth::user()) I decided that one Helper would at least move this into a centralised location. I instantiate the helper to be stored as property across all controllers : $this->authentication_helper
In an example controller: SearchController :
public function index(Request $request): View
{
$this->authentication_helper->getAuthenticationData($this);
//... logic for the search
}
With the AuthenticationHelper doing (amongst other things):
public function getAuthenticationData(Controller $controller) : void
{
$user = Auth::user();
$controller->user = User::with(
organisations.locations', // .. other children .. //
)->find($user->id);
// share to the view
View::share('user', $controller->user);
// ... other $controller property setting
return;
}
I am unsure as to whether this is best practice.
edit - A previously raised second issue been solved - the main question remains:
Is there a problem with this approach - what would be an equivalent way of moving these 20 or so variable assignments to a higher level.
You can get authorized user in controller using Laravel DI, simply your code should be something like this
YourControlle extends Controller
{
public function test(Request $request)
$user = $requset->user(); //use auth user
}
The best approach would be a helper (or multiple helpers, you can create a app\Helpers namespace for that) and have all your logic within it.
The Auth::user() will be accessible from that helper using the Auth Facade, and have your logic there.
An other simpler way is just extending your controllers from a custom base controller you make (which you're going to extend from Controller) and append a $user member to it with a protected visibility, and share it to the view, to be done with in the constructor or in a method to be called through parent::magicMethod()
EDIT
You can override the callAction method used by the controller class
/**
* Execute an action on the controller.
*
* #param string $method
* #param array $parameters
* #return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
// insert your logic here
return call_user_func_array([$this, $method], $parameters);
}
The reason why you can't access the Auth via the constructor is because the session was not fired up yet. You can catch it when it does with this event listener :
Event::listen(Authenticated::class, function ($event) {
$this->user = $event->user;
});
not tested
A middleware with a closure would do the work too. Make it have your logic, and use it in all your controllers.
I have the following problem, I want to share an array to all views in my project so I followed the documentation and it works fine, but I want to get the authenticated user in service provider boot function and it always return null ?
any suggestions ?
this is my code
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public $myusers;
public function boot()
{
$origLat=\Auth::user()->lat;
$origLon=\Auth::user()->lng;
$dist=5;
$lon1=$origLon-$dist/cos(deg2rad($origLat))*73.2044736;
$lon2=$origLon+$dist/cos(deg2rad($origLat));
$lat1=$origLat-($dist/73.2044763);
$lat2=$origLat+($dist/73.2044763);
$id=\Auth::user()->id;
$pictures=User::find($id)->pictures;
$this->myusers = DB::table('users')->select(
DB::raw("*,
3956 * 2 *
ASIN(SQRT( POWER(SIN(($origLat- lat)*pi()/180/2),2)
+COS($origLat*pi()/180 )*COS(lat*pi()/180)
*POWER(SIN(($origLon-lng)*pi()/180/2),2)))*1.609344
as distance"
))
->where('users.id', '!=', \Auth::user()->id)
->whereBetween('lng',[$lon1,$lon2])
->whereBetween('lat',[$lat1,$lat2])
->having("distance", "<", "$dist")
->orderBy("distance")
->get();
view()->share('myusers', $this->myusers);
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
Unfortunately, at this point the Laravel application request lifecycle works in such a way that when the boot method of the App\Providers\AppServiceProvider class is executed the session is not yet initialised (since that's done in a middleware that is executed after the boot method).
Since the authentication systems needs the session in order to get the authenticated user, in your particular case you can't use view()->share() successfully there (although it's the recommended approach). Instead you can use an alternative approach by doing that in a middleware. Here are the steps that you can follow to make this work:
1. Create a middleware class, let's call it LoadUsers, by running this command:
php artisan make:middleware LoadUsers
2. That will generate a class in app/Http/Middleware/LoadUsers.php. Now you just need to move your code from the AppServiceProvider to the handle method of the middleware:
class LoadUsers
{
public function handle($request, Closure $next)
{
// Your code that shares the data for all views goes here
return $next($request);
}
}
3. Next you need to register the middleware with the App\Http\Kernel class. You can add it to the web group from $routeMiddleware if you want to apply the middleware to all routes that that use that or create your specific group or route middleware. So something like this if you want to add it to web:
protected $middlewareGroups = [
'web' => [
...
// Make sure to add this line is after the
// StartSession middleware in this list
\App\Http\Middleware\LoadUsers::class,
],
...
];
Now you should have the proper shared data for all your views that can depend on Auth::user().