Policy in Laravel 5.6 - laravel

I'm trying to have access level control through policy in my Laravel 5.6 application.
I have a Subscriber model and a Company model, Subscribers are only given access to Company by there office locations according to states/region, i.e. a subscriber can view the details of the office if it belongs to the region being assigned to them. for this I have models:
Subscriber
class Subscriber extends Model {
//Fillables and basic attributes being assigned
public function stateIncludeRelation()
{
return $this->belongsToMany('Models\State','subscriber_states',
'subscriber_id', 'state_id');
}
public function user()
{
return $this->belongsTo('Models\User', 'user_id', 'id');
}
}
Company
class Company extends Model {
//Fillables and basic attributes being assigned
public function offices()
{
return $this->hasMany('Models\Company\Office', 'company_id');
}
}
then for Office
class Office extends Model {
//Fillables and basic attributes being assigned
public function company()
{
return $this->belongsTo('Models\Company', 'company_id', 'id');
}}
}
And a common State table:
class State extends Model {
//Fillables and basic attributes being assigned
public function subscriberAccess()
{
return $this->belongsToMany('Models\Subscriber',
'subscriber_states_included_relation',
'state_id', 'subscriber_id');
}
public function companyOffice()
{
return $this->hasOne('Models\Company\Office', 'state', 'id');
}
}
I created a CompanyPolicy something like this:
class CompanyPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the subscriber.
*
* #param User $user
* #param Company $company
* #return mixed
*/
public function view(User $user, Company $company)
{
//Finding subscriber/user state
$userState = State::whereHas('subscriberAccess', function ($q) use($user) {
$q->whereHas('user', function ($q) use($user) {
$q->where('email', $user->email);
});
})->get()->pluck('name');
//Finding company state
$companyState = State::whereHas('companyOffice', function ($q) use($company) {
$q->whereHas('company', function ($q) use($company) {
$q->where('slug', $company->slug);
});
})->get()->pluck('name');
if($userState->intersect($companyState)->all())
return true;
else
return false;
}
}
And registered this to AuthServiceProvider
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'Models\User' => 'Policies\CompanyPolicy',
];
While trying to fetch something like this in my controller:
public function companyGeneral(Request $request)
{
$user = Auth::user();
$company = Company::where('slug', $request->slug)
->with('offices')
->get()->first();
if($user->can('view', $company))
return response()->json(['data' => $company], 200);
else
return response()->json(['data' => 'Unauthorised'], 403);
}
Everytime I am getting Unauthorised response. Guide me into this. Thanks

Related

Testing BelongsToMany relationship with pivot table

I'm trying to figure out how to create all the data based on this relationship testing in Laravel.
Company Model
class Company
{
public function stores()
{
return $this->hasMany(Store::class, 'company_id');
}
public function employers()
{
return $this->belongsToMany(User::class, 'employers',
'company_id', 'user_id');
}
}
Store Model
class Store
{
public function company()
{
return $this->belongsTo(Company::class, 'company_id');
}
public function employers()
{
return $this->belongsToMany(User::class, 'employers',
'store_id', 'user_id');
}
}
User Model
class User
{
public function company()
{
return $this->belongsToMany(Company::class, 'employers',
'user_id', 'company_id');
}
public function store()
{
return $this->belongsToMany(Store::class, 'employers',
'user_id', 'store_id');
}
}
$company = Company::factory()->hasStores(
Store::factory()->hasEmployers(User::factory())
)->create();
dd($company) // App\Models\Company {#2470... Ok!
$store = $company->store()->first();
dd($store) // App\Models\Store {#2479... Ok!
$user = $store->employers()->first();
dd($user) // null (T-T)
Background: this is an application that allows a proprietor to own several companies. For that reason, I got many relationships, and even so, employees sometimes can only belong to a single company or store.
Try:
$store = $company->store->first();
dump($store);
$user = $store->employers->first();
dump($user);
Try something like this with DB Facade
private $employ;
public function setUp(): void
{
$this->employ = Employ::factory()->create([
'id' => 14,
'name' => 'Name Employ'
]);
}
public function test_pivote_table()
{
$user = User::factory()->create([
'name' => 'User test'
);
//here :)
DB::table('name_pivote_table')->insert([
'user_id' => $user->id,
'employ_id' => $this->employ->id
]);
}
Credits to Fguzman :)

Creating a new record using a One to Many relation in Laravel

I have a sample relation between two models, User and Announcement as displayed below.
class Announcement extends Model
{
//
protected $guarded = [];
public function user(){
return $this->belongsTo(User::class);
}
}
class User extends Model
{
//
protected $guarded = [];
public function announcements(){
return $this->hasMany(Announcement::class);
}
}
Currently I am trying to create a new announcement, using the relation but it throws an error
"message": "Call to a member function announcements() on null",
This is the current state of my api from the controller
class AnnouncementController extends Controller
{
public function store(Request $request)
{
//
$record = request()->user()->announcements()->create($this->validateRequest());
return (new AnnouncementResoure($record))
->response()
->setStatusCode(Response::HTTP_CREATED);
}
private function validateRequest()
{
return request()->validate([
'title'=> 'required|Min:3',
'comment' => 'required'
]);
}
}
I don't seem to know what could e responsible for the error.
instead of request()->user() replace it with auth('api')->user(); or $request->user('api');

type casting in laravel json response in relationships eager loading

This is my post model.
class Post extends Model
{
use SoftDeletes;
protected $table = 'posts';
protected $fillable = ['title','featuring_image', 'brief', 'body', 'seen_count'];
public function user(){
return $this->belongsTo(User::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function someComments()
{
return $this->comments()->limit(Constants::COMMENTS_COUNT_LIMIT);
}
public function commentsCount()
{
return $this->comments()
->selectRaw('post_id, count(*) as count')
->groupBy('post_id');
}
public function likes()
{
return $this->hasMany(Like::class);
}
public function isLiked()
{
return $this->likes()->where('user_id', auth()->check() ? auth()->user()->id : 0);
}
public function likesCount()
{
return $this->likes()
->selectRaw('post_id, count(*) as count')
->groupBy('post_id');
}
}
I executed this query on this model.
$post = Post::with(['categories', 'user', 'commentsCount', 'likesCount', 'isLiked'])->find($post->id);
Because of the relation between this table and like and comment table, The output of this query for 'commentsCount', 'likesCount', 'isLiked' is an array. But I need to receive numbers for 'commentsCount' and 'likesCount', and a boolean for 'isliked' as an output, in laravel josn response.
You might find it easier to use the withCount() the comes with Eloquent instead.
Then for is_liked you could use a scope to get the value and the cast it to a boolean:
public function scopeIsLiked($query)
{
if (is_null($query->getQuery()->columns)) {
$query->select([$query->getQuery()->from . '.*']);
}
$relation = Relation::noConstraints(function () {
return $this->likes();
});
$q = $this->likes()->getRelationExistenceCountQuery(
$relation->getRelated()->where('user_id', auth()->check() ? auth()->user()->id : 0)->newQuery(), $query
);
$query->selectSub($q->toBase(), 'is_liked');
}
Please note you will need to add the use statement for Relation to the top of the class:
use Illuminate\Database\Eloquent\Relations\Relation;
You model could then look like:
class Post extends Model
{
use SoftDeletes;
protected $table = 'posts';
protected $fillable = ['title', 'featuring_image', 'brief', 'body', 'seen_count'];
public function user()
{
return $this->belongsTo(User::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function someComments()
{
return $this->comments()->limit(Constants::COMMENTS_COUNT_LIMIT);
}
public function likes()
{
return $this->hasMany(Like::class);
}
/**
* Scope to add the "is_liked" flag.
*
* #param $query
*/
public function scopeIsLiked($query)
{
if (is_null($query->getQuery()->columns)) {
$query->select([$query->getQuery()->from . '.*']);
}
$relation = Relation::noConstraints(function () {
return $this->likes();
});
$q = $this->likes()->getRelationExistenceCountQuery(
$relation->getRelated()->where('user_id', auth()->check() ? auth()->user()->id : 0)->newQuery(), $query
);
$query->selectSub($q->toBase(), 'is_liked');
}
}
And your query would look something like:
$post = Post::with('categories', 'user')
->withCount('likes', 'comments')
->isLiked()
->find($post->id);
Hope this helps!
You can use Laravel casts:
Inside the each model you can add the following to cast a value, per example:
protected $casts = [
'isLiked' => 'boolean',
];
Rwd's answer gives a nice solution using scopes, but for laravel 5.4+ you could get away with aliasing the withCount() result and then casting it to boolean with a $cast variable on the model or an accessor (with accessor, you can only get snake case is_liked). This way we don't need to write complex scopes.
The model would be
class Post extends Model
{
// rest of model
protected $casts = ['isLiked'=>'boolean'];
public function likes()
{
return $this->hasMany(Like::class);
}
}
Then in your controller
$post = Post::with('categories', 'user')
->withCount(
[
'likes as likesCount', 'comments as commentsCount',
'likes as isLiked' =>function($query){
$query->where('user_id', auth()->check() ? auth()->user()->id : 0);
}
]
)
->find($post->id);
And now you get likesCount (int), commentsCount (int) and isLiked (boolean)

How do I load a collection in a model then query it with the query builder

I have create a morphMany relationship for ratings and I'm having a problem loading the ratings relationship data inside the model using the model->load or model::with method both of them aren't letting me use the collections model builder.
if I do this inside a method of a model it throws an error:
$all = this->ratings()->get();
return $all;
Call to undefined method Illuminate\Database\Query\Builder::ratingInfo()
I need the ratings query builder so I can then query and filter the results but It's not using the query builder and even if I make this a scope it's still throws the same error.
all code:
class Product extends Model
{
use Rateable;
protected $table = "products";
protected $fillable = [
'title',
'sku',
'quantity',
'unit_price',
'created_by', 'updated_by'
];
public function created_by() {
return $this->belongsTo('App\User', 'created_by', 'id');
}
public function updated_by() {
return $this->belongsTo('App\User', 'updated_by', 'id');
}
public function ratings() {
return $this->morphMany('App\Rating', 'rateable');
}
public function ratingInfo() {
$all = $this->ratings()->get() error using get request for eager loading;
// i want to query like this
$two_star = $all->filter(function ($item, $key) {
return $item->rating === 2;
});
return $all;
}
}
public function show($id) {
$product = Product::findOrFail($id);
// it doesn't seem to matter if use the keyword ::with('ratingInfo')
$product->load('ratingInfo', 'created_by', 'updated_by');
return response()->json($product, 200, ['Content-Length' => strlen(json_encode($product))]);
}
class Rating extends Model
{
protected $table = 'ratings';
protected $fillable = ['rating', 'comment', 'user_id', 'rateable_id', 'rateable_type'];
public function rating()
{
return $this->morphTo();
}
}
Using phone numbers and user and companies as an example:
class PhoneNumber extends Model
{
/**
* Get all of the owning callable models.
*/
public function callable()
{
return $this->morphTo();
}
}
class Company extends Model
{
/**
* Get all of the model's phone numbers.
*
* #return mixed
*/
public function phoneNumbers()
{
return $this->morphMany(PhoneNumber::class, 'callable');
}
}
class User extends Model
{
/**
* Get all of the model's phone numbers.
*
* #return mixed
*/
public function phoneNumbers()
{
return $this->morphMany(PhoneNumber::class, 'callable');
}
}
To save a phone number to a user or company would be like this:
$phoneNumber = new PhoneNumber(['number' => '555-555-5555']);
$user->phoneNumbers()->save(phoneNumber);
$phoneNumber = new PhoneNumber(['number' => '555-555-5555']);
$company->phoneNumbers()->save(new PhoneNumber(phoneNumber));
Then to access the phone number collections associated with each, simply:
$user->phoneNumbers // this is a Collection
$company->phoneNumbers // this is a Collection
$user->phoneNumbers->count() // access to all Collection methods as this point

can't attach topic_id in the comment table

I am making a forum where users can create topics and leave a reply just like this forum.
I made a relationship just like below.However, when I save an article the topic_id does not get attached.I think the saveReply method is wrong.
Also,in this case how do you pass comments on the particular post to the view in the show method??
I am a noob,so if my question is vague I am sorry,but any help will be appreciated!!
Route
Route::group(['middleware' => 'web'], function () {
Route::get('forums','ForumsController#index');
Route::get('forums/create','ForumsController#create');
Route::post('forums', 'ForumsController#store');
Route::get('forums/{category_id}/{title}','ForumsController#show');
Route::post('forums/{category_id}/{title}', 'ForumsController#saveReply');
});
forumcontroller
class ForumsController extends Controller
{
public function index()
{
$categories = Category::all();
$topics = Topic::latest()->get();
return view('forums.index',compact('categories','topics'));
}
public function create()
{
$categories = Category::lists('title', 'id');
return view('forums.create', compact('categories'));
}
public function store(Request $request)
{
Auth::user()->topics()->save(new Topic($request->all()));
flash()->success('投稿しました','success');
return redirect('forums');
}
public function show($category_id, $title)
{
Topic::where(compact('category_id','title'))->first();
return view('forums.post', compact('topic'));
}
public function saveReply (Request $request)
{
Auth::user()->comments()->save(new Comment($category_id,$request->all()));
flash()->success('投稿しました','success');
return redirect()->back();
}
}
topic model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class topic extends Model
{
protected $fillable = [
'title',
'body',
'category_id'
];
public function category()
{
return $this->belongsTo('App\category');
}
public function user()
{
return $this->belongsTo('App\User');
}
public function comments()
{
return $this->hasMany('App\Comment');
}
}
user model
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function articles()
{
return $this->hasMany('App\Article');
}
public function topics()
{
return $this->hasMany('App\Topic');
}
public function comments()
{
return $this->hasMany('App\Comment');
}
}
comment model
class Comment extends Model
{
protected $fillable = [
'reply',
'user_id',
'topic_id'
];
public function topic()
{
return $this->belongsTo('App\Topic');
}
public function user()
{
return $this->belongsTo('App\User');
}
}
comment table
class CreateCommentsTable extends Migration
{
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->text('reply');
$table->integer('user_id')->unsigned();
$table->integer('topic_id')->unsigned();
$table->timestamps();
});
}
public function down()
{
Schema::drop('comments');
}
}
The Request::all returns an array of all inputs so when you are doing:
new Comment($category_id,$request->all())
You'll get something like this:
1['some' => 'thing', 'other'=> 'values']
Which could be the problem so try this instead:
new Comment(array_merge(['category_id' => $category_id ], $request->all())
When on development/local environment, set the debug true so you'll get meaningful error messages so you can findout the exect problem easily.

Resources