Suppose there is a Test Model like this:
class Test extends Model
{
public $primaryKey = 'test_id';
public function questions ()
{
return $this->belongsToMany('App\Question', 'question_test', 'test_id', 'question_id')->withPivot('weight');
}
}
And a Question Model like this :
class Question extends Model
{
public $primaryKey = 'question_id';
public function tests ()
{
return $this->belongsToMany('App\Test', 'question_test', 'question_id', 'test_id')->withPivot('weight');
}
}
Question model fields are these:
question_id
text
correct
active => can be true or false
created_at
updated_at
As you see there is a ManyToMany Relationship between these two models.
And I have two separate sections in my app, one for Admin users and other for public users.
Admin can perform any action on questions and tests. like add some question to tests , remove , edit and etc.
But in the other hand , public users can only tests and related question that are active(means their active fields are true).
Suppose some Admin routes are these:
http://myapp.dev/Admin/tests
http://myapp.dev/Admin/test/5/questions
http://myapp.dev/Admin/test/5/question/create
http://myapp.dev/Admin/test/5/remove
And some User routes are:
http://myapp.dev/Dashboard
http://myapp.dev/tests-list
http://myapp.dev/test/5/questions
For that , I know that I can use query-scopes like this in Question model :
public function scopeActive($query)
{
return $query->where('active', 1);
}
And when I want to fetch only active questions must to do this:
$test->questions->active()->get();
But I have many action that perform on questions on the user Panels therefore that is hard and time consuming if want to use an active() method for select questions.
I can not use Global scopes because that Affects on all question queries that run in whole project.
Is there a way that can define global (or local ) scope for some specific routes and sub routes that public users can see those?
Or there are other ways to solve this problem?
Update:
In addition to those mentioned, Users can have some roles. For example an admin user can switch from admin panel to user panel. in this case I want to display only active question when he is on user panel.
You can do this by wrapping your routes in middleware. Create HideInactiveQuestions.php by running php artisan make:middleware HideInactiveQuestions
<?php
namespace App\Http\Middleware;
use App\Question;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class HideInactiveQuestions
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
Question::addGlobalScope('active', function(Builder $builder) {
$builder->where('active', '=', 1);
});
return $next($request);
}
}
Next, register your middleware in Kernel.php
protected $routeMiddleware = [
....
'restrict.public' => \App\Http\Middleware\HideInactiveQuestions::class,
];
Now those routes which have been wrapped by this middleware (restrict.public) will be filtered by your scopes.
Related
i have at least 8 components have same relation with course model i want to un return this component if this course is hidden
i tried to make it in global scope but still need to do in all these component's model whereHas and with how can i do these in scope in which model?
i don't want to repeat these relation in all component maybe once in maybe Global Scope or something like that
Note: i worked with laravel 5.8
i must repeat this in all components like material
$callQuery=function($q) use ($request){
if(!$request->user()->can('course/show-hidden-courses'))
$q->where('show',1);
};
// $material = $materials_query->with(['lesson','course.attachment'])->whereIn('lesson_id',$lessons);
$material = $materials_query->whereHas('course',$callQuery)->with(['lesson','course' => $callQuery]);
I am not sure, if I understood your issue well. But, if you are looking for using the same whereHas everywhere. You can just create a scope in your model, basically like:
public function scopeCourse($query){
$query->whereHas('course', function ($q){
$q->where('show', 1);
});
}
And andywhere you like to have this filter, you can just query like:
$materials_query->course()->with([...]);
Besides, if you want to enforce it everywhere, you can just create a Global Scope in for example; App\Model\Scope with the bellow code:
<?php
namespace App\Model\Scope;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class CourseScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
return $builder->whereHas('course', function ($q){
$q->where('show', 1);
});
}
}
And then in your model, you can just:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new CourseScope);
}
And then, you can just write your query like:
$materials_query->with([...]);
The scope will be automatically enforced without actually calling the scope, in our case course(). There will be times that you won't need the enforce, so you can call like:
$materials_query->withoutGlobalScope();
I have four table users, groups, posts, and group_user. Users can follow different groups. The group_user table is for many-to-many relationship.
Every post belongs to a group and a user. I want that a user can only post a post on a group if he follows that group. I can easily check using if statement that whether a user follows that group or not. But how can I authorize the user for posting using policies.
Create a policy:php artisan make:policy.
Register policy:
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
In your policy the logic, for instance:
public function create(User $user, Post $post)
{
$groupsOfUser = $user->groups()->get();
foreach ($groupsOfUser as $group) {
if($group->id == request('groupId'))return true;
}
}
And in your Controller:
public function store(Post $post, $groupId)
{
$this->authorize('create', $post);
Post::create([
'user_id' =>auth()->id(),
'group_id' => $groupId,
'title' => 'sometitle',
]);
}
And i have tested it with route:
Route::get('/post/{groupId}', 'PostController#store');
But you may be getting groupId via input, but you get the idea.
There doesn't seem to be any good answers for this so I'll share what I did to help out anyone looking for this in the future.
This was tested in laravel 5.8 but I think it will work at least a few versions back.
First create a pivot model
use Illuminate\Database\Eloquent\Relations\Pivot;
class GroupUser extends Pivot {}
Update your groups relationship in the User model
public function groups() {
return $this->belongsToMany(Group::class)
->using(GroupUser::class);
}
And update your users relationship in the Group model
public function users() {
return $this->belongsToMany(User::class)
->using(GroupUser::class);
}
Create your GroupUserPolicy class
use Illuminate\Auth\Access\HandlesAuthorization;
class GroupUserPolicy {
use HandlesAuthorization;
public function update(User $user, GroupUser $pivot) {
// check permissions
}
// additional authorization methods
}
And link them up in the AuthServiceProvider if not using auto discover
protected $policies = [
GroupUser::class => GroupUserPolicy::class,
// ...
];
Then when you want to show or update a pivot, in a controller for example, then you can just pass the pivot to the authorize check
$group = $user->groups()->where('group_id', $groupId)->firstOrFail();
$this->authorize('update', $group->pivot);
// or ...
foreach ($user->groups as $group) {
$this->authorize('update', $group->pivot);
}
There is a complex solution for Access Control List. Read about Zizaco Entrust Library. Here you can set permissions for each route in your system. By that routing and permission you can prepare few groups.
Topic is hard but realy worth to implement:
https://github.com/Zizaco/entrust
On my app, I'm trying to make it so that if a user has a certain condition, he will ALWAYS be redirected to a certain page, no matter which route he tries to access. In this case, it's if he doesn't have a username (long story).
ComposerServiceProvider.php :
public function boot() {
View::composer('templates.default', function ($view) {
if(Auth::user()) {
if (Auth::user()->username == null || Auth::user()->username == "") {
return redirect()->route('auth.chooseUsername');
}
So I figured the place to do this would be
ComposerServiceProvider.php.
However, I'm noticing that my redirect don't work in ComposerServiceProvider.php. And laravel.log doesn't give me an error or reason why.
The if condition is being met. If I replace return redirect()->route('auth.chooseUsername'); with dd('test');, sure enough all my pages return 'test'.
Why is this happening?
Try this steps:
You can use middleware for this scenario like below:
Create middleware php artisan make:middleware CheckPoint
Inside App\Http\Middleware\CheckPoint.php File
use Closure;
class CheckPoint
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if($request->user()) {
if ($request->user()->username == null || $request->user()->username == "") {
return redirect()->route('auth.chooseUsername');
}
}
return $next($request);
}
}
2. Add the middleware inside the app\Http\kernel.php
protected $routeMiddleware = [
'checkPoint' => \App\Http\Middleware\CheckPoint::class,
];
Then you can use it inside your route file and controller like below
Route::get(...)->middleware('checkPoint');
Route::middleware('checkPoint')->group(function() {
//Group of routes
.....
});
More About Middleware
controller middleware
In App\Http\Middleware create a new middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckYourCondition
{
public function handle($request, Closure $next)
{
if (! $request->user()->yourCondition()) {
return redirect('your_target_routre');
}
return $next($request);
}
}
Register your middleware by adding it to protected $routeMiddleware in App\Http\Kernel.
and assing it to the 'web' middleware group in protected $middlewareGroups.
For details see
The ComposerServiceProvider has a different purpose. It is used to register View Composers.
View composers are callbacks or class methods that are called when a
view is rendered. If you have data that you want to be bound to a view
each time that view is rendered, a view composer can help you organize
that logic into a single location.
See View Composers.
Im still fairly new to Laravel and have worked through some of the fundamental laracasts. Now I'm starting my first laravel project but I am stuck on how to use my first package "Landlord". Basically I need a setup for Multi-Tenants in my application. I have a company table and a user table, the user table has a company_id column. When a company registers it successfully creates the company and attaches the company_id to the user.
I assume Landlord is the best way to implement a multi-tenant application so I worked through the installation instructions and now I have it included in my app.
However the first line in the USAGE section says:
IMPORTANT NOTE: Landlord is stateless. This means that when you call addTenant(), it will only scope the current request.
Make sure that you are adding your tenants in such a way that it
happens on every request, and before you need Models scoped, like in a
middleware or as part of a stateless authentication method like OAuth.
And it looks like I need to attach a the Landlord::addTenant('tenant_id', 1); facade.
This may be a pretty simple answer I am overlooking but where is the best place to to use addTenant and do I have to redeclare it with every controller or model? Should I attach it when the user signs in, use it in my routes or use as a middleware? If it is a middleware is the following correct in order to pull the company_id from the current user and use it with addTenant?
Middleware:
public function handle($request, Closure $next){
$tenantId = Auth::user()->tenant_id;
Landlord::addTenant('tenant_id', $tenantId);
return $next($request);
}
UPDATE
Here is my middleware (MultiTenant.php)
<?php
namespace App\Http\Middleware;
use Closure;
use App\User;
use Illuminate\Support\Facades\Auth;
class MultiTenant
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::check()) {
$tenantId = Auth::user()->company_id;
Landlord::addTenant('company_id', $tenantId); // Different column name, but same concept
}
return $next($request);
}
}
My routes/web.php
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of the routes that are handled
| by your application. Just tell Laravel the URIs it should respond
| to using a Closure or controller method. Build something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::group(['middleware' => ['multitenant']], function () {
Route::get('/home', 'HomeController#index');
//Clients
Route::resource('clients', 'ClientController');
});
My Client.php Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use HipsterJazzbo\Landlord\BelongsToTenants;
class Client extends Model
{
use BelongsToTenants;
//
protected $fillable = [
'organization',
];
}
https://github.com/HipsterJazzbo/Landlord#user-content-usage
While just one option, I also went the middleware route. I saw it as an easy way to implement it.
I added the middleware to my routes/web.php file:
Route::group(['middleware' => ['landlord']], function () {
// Your routes
});
And my landlord middleware looks like this:
public function handle($request, Closure $next)
{
if (Auth::check()) {
$tenantId = Auth::user()->company_id;
Landlord::addTenant('company_id', $tenantId); // Different column name, but same concept
}
return $next($request);
}
Then I just add the trait to the models that I want scoped:
use HipsterJazzbo\Landlord\BelongsToTenant;
class User extends Authenticatable
{
use BelongsToTenant;
}
Update
Also, make sure in your config/app.php file you have added landlord to the providers array:
'providers' => [
// ...
HipsterJazzbo\Landlord\LandlordServiceProvider::class
// ...
],
And to the aliases array:
'aliases' => [
// ...
'Landlord' => HipsterJazzbo\Landlord\Facades\Landlord::class,
// ...
],
Then finally composer dump-autoload when completed to refresh the autoloading.
In my application I use Laravel's authentication system and I use dependency injection (or the Facade) to access the logged in user. I tend to make the logged in user accessible through my base controller so I can access it easily in my child classes:
class Controller extends BaseController
{
protected $user;
public function __construct()
{
$this->user = \Auth::user();
}
}
My user has a number of different relationships, that I tend to eager load like this:
$this->user->load(['relationshipOne', 'relationshipTwo']);
As in this project I'm expecting to receive consistently high volumes of traffic, I want to make the application run as smoothly and efficiently as possible so I am looking to implement some caching.
I ideally, need to be able to avoid repeatedly querying the database, particularly for the user's related records. As such I need to look into caching the user object, after loading relationships.
I had the idea to do something like this:
public function __construct()
{
$userId = \Auth::id();
if (!is_null($userId)) {
$this->user = \Cache::remember("user-{$userId}", 60, function() use($userId) {
return User::with(['relationshipOne', 'relationshipTwo'])->find($userId);
});
}
}
However, I'm unsure whether or not it's safe to rely on whether or not \Auth::id() returning a non-null value to pass authentication. Has anyone faced any similar issues?
I would suggest you used a package like the following one. https://github.com/spatie/laravel-responsecache
It caches the response and you can use it for more than just the user object.
Well, after some messing about I've come up with kind of a solution for myself which I thought I would share.
I thought I would give up on caching the actual User object, and just let the authentication happen as normal and just focus on trying to cache the user's relations. This feels like quite a dirty way to do it, since my logic is in the model:
class User extends Model
{
// ..
/**
* This is the relationship I want to cache
*/
public function related()
{
return $this->hasMany(Related::class);
}
/**
* This method can be used when we want to utilise a cache
*/
public function getRelated()
{
return \Cache::remember("relatedByUser({$this->id})", 60, function() {
return $this->related;
});
}
/**
* Do something with the cached relationship
*/
public function totalRelated()
{
return $this->getRelated()->count();
}
}
In my case, I needed to be able to cache the related items inside the User model because I had some methods inside the user that would use that relationship. Like in the pretty trivial example of the totalRelated method above (My project is a bit more complex).
Of course, if I didn't have internal methods like that on my User model it would have been just as easy to call the relationship from outside my model and cache that (In a controller for example)
class MyController extends Controller
{
public function index()
{
$related = \Cache::remember("relatedByUser({$this->user->id})", 60, function() {
return $this->user->related;
});
// Do something with the $related items...
}
}
Again, this doesn't feel like the best solution to me and I am open to try other suggestions.
Cheers
Edit: I've went a step further and implemented a couple of methods on my parent Model class to help with caching relationships and implemented getter methods for all my relatonships that accept a $useCache parameter, to make things a bit more flexible:
Parent Model class:
class Model extends BaseModel
{
/**
* Helper method to get a value from the cache if it exists, or using the provided closure, caching the result for
* the default cache time.
*
* #param $key
* #param Closure|null $callback
* #return mixed
*/
protected function cacheRemember($key, Closure $callback = null)
{
return Cache::remember($key, Cache::getDefaultCacheTime(), $callback);
}
/**
* Another helper method to either run a closure to get a value, or if useCache is true, attempt to get the value
* from the cache, using the provided key and the closure as a means of getting the value if it doesn't exist.
*
* #param $useCache
* #param $key
* #param Closure $callback
* #return mixed
*/
protected function getOrCacheRemember($useCache, $key, Closure $callback)
{
return !$useCache ? $callback() : $this->cacheRemember($key, $callback);
}
}
My User class:
class User extends Model
{
public function related()
{
return $this->hasMany(Related::class);
}
public function getRelated($useCache = false)
{
return $this->getOrCacheRemember($useCache, "relatedByUser({$this->id})", function() {
return $this->related;
});
}
}
Usage:
$related = $user->getRelated(); // Gets related from the database
$relatedTwo = $user->getRelated(true); // Gets related from the cache if present (Or from database and caches result)