I got a rather simple application where a user can report other users comments and recipes. I use a polymorphic relation to store the reports. Which works fine; however, I am now trying to get the offences that a user has made.
Getting users reports is not a problem, this can be done directly using user->reports() but I would very much like to get the reports in which other people has reported said user. I can get this to work using either the hasManyThrough relation or queries BUT only on one model at a time.
ex.
public function offenses() {
return $this->hasManyThrough('Recipe', 'Reports');
}
Or
->with('user.recipe.reports')
The problem is that my reportable object is not just recipes, it could be comments, images, etc. So instead of having to use multiple functions, the logical way would be to parse the relationship between hasManyThrough various parameters somehow.
Theoretically looking like this:
public function offenses() {
return $this->hasManyThrough(['Recipe', 'RecipeComments'], 'Reports');
}
Is this in any way possible? With some undocumented syntax? If not is there any clever workarounds/hacks?
Possible solution?
Would an acceptable solution be to add another column on my report table and only add offender_id like this?
ID | User_id | Offender_id | Reportable_type | Reportable_id
That would mean I could just make a relation on my user model that connects offences through that column. But would this be considered redundant? Since I already have the offender through the reportable model?
Models
Polymorphic Model
class Report extends Model {
public function reportable() {
return $this->morphTo();
}
public function User() {
return $this->belongsTo('App\User');
}
}
Recipe Model
class Recipe extends Model {
public function user() {
return $this->belongsTo('App\User');
}
public function reports() {
return $this->morphMany('App\Report', 'reportable');
}
}
Comment Model
class RecipeComment extends Model {
public function user() {
return $this->belongsTo('App\User');
}
public function reports() {
return $this->morphMany('App\Report', 'reportable');
}
}
Using your current model, you can receive all the reported models for a user by the following code:
$recipeCommentReport = RecipeComment::whereHas('reports',function($q){
return $q->where('user_id','=',Auth::user()->id)
});
$recipeReport = Recipe::whereHas('reports',function($q){
return $q->where('user_id','=',Auth::user()->id)
});
//Get all reports into one
$reports = $recipeReport->merge([$recipeCommentReport]);
This is messy at best, because:
We can't sort the results, given that we are using two separate db queries.
If you have other models who have a report relationship, just imagine the chaos.
The best solution, is as you have figured out above:
Add an offender_id column to your report table.
It is cleaner and follows the DRY principle.
Typical Case Scenarios
Get all Recipe Comment reports for User
Report::where('offender_id','=',Auth::check()->id)
->where('reportable_type','=','RecipeComment')
->get();
Count offense by type for User
Report::where('offender_id','=',Auth::check()->id)
->grouBy('reportable_type')
->select(Db::raw('count(*)'),'reportable_type')
->get();
Related
I'm trying to use a HasMany relation in a HasOne.
I have following Models:
class Auction extends Model
{
//...
public function bids(): HasMany
{
return $this->hasMany(Bid::class, 'auction_id');
}
public function approvedBids(): HasMany
{
return $this->bids()->approved();
}
public function topBids(): HasMany
{
return $this->approvedBids()->orderByDesc('price')->take(10);
}
public function topBid(): HasOne
{
//return $this->topBids()->firstOfMany(); // Not Working
//return $this->hasOne(Bid:class, 'auction_id)->ofMany('price','max')->approved(); // not working
//return $this->hasOne(Bid:class, 'auction_id)->approved()->ofMany('price','max'); // not working
//return $this->hasOne(Bid::class, 'auction_id')->ofMany('price', 'max'); // working but not as I expecting
}
}
class Bid extends Model
{
//...
public function scopeApproved(Builder $query): Builder
{
return $query->where('state', BidState::STATE_APPROVED);
}
//...
}
As you can see in the source, I'm looking for a way to make a relation that retrieve the Top Bid (ONE BID) from topBids() relation, but I don't know how, and none of my approaches works:
$this->topBids()->firstOfMany(); // Not Working
$this->hasOne(Bid:class, 'auction_id')->ofMany('price','max')->approved(); // not working
$this->hasOne(Bid:class, 'auction_id')->approved()->ofMany('price','max'); // not working
Unfortunately these shouldn't be a relationships
Real question is why are you trying to make these relationships?
Usually you should be using relationships on model to describe how they are correlating together within the database, the rest of the things you should be defining as a scope on a query or a model, or as an attribute.
So, what I'm trying to say is this:
Keep bids as a relationship, as that is actually a relationship to the Bid model
Update approvedBids to be a scope (or an attribute)
Update topBids to be a scope (or an attribute)
Then, you will be able to find top bid easily by doing something like this:
$this->topBids->first() -> if it is an attribute
$this->topBids()->first() -> if it is a scope
This is how you can create a scope: https://laravel.com/docs/9.x/eloquent#local-scopes
In the end, you can even create an attribute that will allow you to retrieve topBid like this:
public function getTopBidAttribute(){
$this->bids()->approved()->orderByDesc('offered_token_price')->first();
}
Then later you can just do $this->topBid.
I think I've found the solution
public function topBid(): HasOne
{
return $this->hasOne(Bid::class, 'auction_id')
->approved()
->orderByDesc('price');
}
You see the problem was in ofMany() function, which creates a huge SQL and I don't know why!
I've returned a HasOne object here, which supports all kinds of query manipulations. Basically HasOne class, tells the main query, to:
Retrieve the first record of the query I've provided.
So if we use orderBy it only provides an order for HasOne's query. and the main query will take cares of the rest and selects the first record.
My business can have many pages and here i am defining my model:
class PageList extends Model
{
public function business()
{
return $this->hasMany('App\Business','business_id');
}
}
My Controller
public function pageListHere()
{
$list = PageList::all();
return view('page-list',compact('list'));
}
and my view having for each loop
<td>{{optional($value->business)->business_url}}.spikesales.io/{{$value->page_url}}</td>
In my relation ship if i do hasOne('App\Business','business_id'); it works fine but its only display one page against business but i have multiple pages in my database and one business can have many pages and if write hasMany('App\Business','business_id'); it show me the error Property [business_url] does not exist on this collection instance
I want to show the multiple pages against one business.
your help will be highly appreciated!
I think you are a little bit confused. In your model, you are actually defining the relation A page has many businesses, and you are telling that the businesses table has a column business_id, which Laravel tried to find but couldn't, resulting in an error.
The hasMany function works in this way:
// In the Post Model...
public function comments() {
return $this->hasMany('App\Comment', 'post_number');
}
This makes the relation A Post has many Comments and makes Laravel assume that the comments table has post_number column.
You want to show multiple pages against one business. So, A business has many Pages. To code it...
// In your BUSINESS model...
public function pages() {
// Notice that $this refers to the Business Model
return $this->hasMany('App\PageList', 'business_id');
}
You can optionally (but I think you should) omit the 'business_id' part. It will safely be assumed by default.
And the inverse of the relation, A Page belongs to A business, will be coded as...
// In your PAGELIST model...
public function business() {
return $this->belongsTo('App\Business');
}
You need to loop/iterate over the business relationship since the result will return in a array of collection:
#foreach($lists as $list)
// others markup
#foreach($list->business as $business)
// do something here
{{ $business->business_url }}
#endforeach
#endforeach
I'm playing with laravel for the first time and i'm a little confused on how to achieve a link table relationship. I have 3 tables: Tank, TankContent and Content
I have the following models:
Tank:
class Tank extends Model
{
public function getTankContent()
{
return $this->hasMany('App\TankContent', 'tankID', 'tankID');
}
}
TankContent:
class TankContent extends Model
{
public function getTank()
{
return $this->belongsTo('App\Tank', 'tankID', 'tankID');
}
public function getContent()
{
return $this->belongsTo('App\Content', 'contentID', 'contentID');
}
}
Content:
class Content extends Model
{
public function getContentTanks()
{
return $this->hasMany('App\ContentTank', 'contentID', 'contentID');
}
}
Now im trying to call say tankID 2 and get all the content details inside of that
$content = Tank::find(2);
$items = $content->getTankContent;
This will list me the content. But then how do i go about linking the results to getContent() from the TankContent model?
Thanks in advance. Hopefully I just need it explaining and then it will all click.
p.s i have tried reading the https://laravel.com/docs/5.5/eloquent-relationships#many-to-many and im still stumped!
TankContent isn't required, the belongsToMany relationship methods belong on the Tank and Content models.
https://laravel.com/docs/5.5/eloquent-relationships#many-to-many
From here you can see there is no RoleUser model, the models are only Role and User. The belongsToMany relationship on those models defines that the intermediate, or pivot, table is role_user.
You can additionally define other fields on that relationship with withPivot() chained onto the belongsToMany method. (read "Retrieving Intermediate Table Columns"). Therefore, there is likely no reason to have a separate model for the intermediate/pivot table.
I have model called Organisation, which has a many to one relationship with a model called Client, it looks like this,
public function clients() {
return $this->hasMany('Client', 'owner_id')->orderBy('name', 'asc');
}
Client has a one to many relationship with projects that looks like this,
public function projects()
{
return $this->hasMany('Project');
}
The project model / table as a column called total_cost within an organisation I can gets it's clients, and then the clients projects, what I wanting to do is get the value of the client by aggregating all the clients projects total_cost entries, I am trying to do this with the following,
public function clientsValue() {
return $this->clients()->projects()->select(DB::raw("SUM(total_cost) as client_value"));
}
In my mind this getting the projects through the clients relationship and then running a select on the project model, however I am getting the following error,
Call to undefined method Illuminate\Database\Query\Builder::projects()
But I am not sure why as the clients has a projects relationship.
$this->clients() returns relation definition - object of class HasMany, not a Client object. HasMany class does not have a projects() method, that's why you're getting an error.
In order to calculate total clients value, you first need to define additional relation between organization and projects:
public function projects() {
return $this->hasManyThrough('Project', 'Client');
}
This will tell Eloquent how to fetch projects for given organization using intermediate Client model.
Once you have the relation you can call aggregate functions on it:
public function clientsValue() {
return $this->projects()->sum('total_cost');
}
You can read more about aggregate functions here: http://laravel.com/docs/5.1/queries#aggregates
You can read more about hasManyThrough relations here: http://laravel.com/docs/5.1/eloquent-relationships#has-many-through
I have two models in my Laravel 4.2 web application, User and Group. A user can be a member of many groups, and a group can have many members. Both models are thus joined with a many-to-many relationship:
<?php
class User extends Eloquent {
public function groups()
{
return $this->belongsToMany('Group');
}
}
class Group extends Eloquent {
public function users()
{
return $this->belongsToMany('User');
}
}
?>
One of my API resources is /groups, which lists all groups available within the app:
<?php
$groups = Group::with('users')->all();
?>
This works, however in the JSON response each user contains all fields from the users table (excluding of course those in the $hidden attribute). I would like this relationship to return only a specific set of fields instead of the whole table.
In other relationship types I can easily achieve this with the following statement (assume now that users may belong to only one group):
<?php
public function users()
{
return $this->hasMany('User')->select(['id', 'first_name', 'last_name']);
}
?>
However the above does not seem to work with many-to-many relationships. I came across this question which apparently refers to the same issue and it looks like this was not possible in Laravel 4.1. The author of the chosen answer, tptcat, provides a link to an issue on Laravel's Github issue tracker, but the link is no longer working and I couldn't figure whether this issue is still open in 4.2.
Has anybody come across this and successfully managed to solve it?
{
return $this->belongsToMany('User')->select(array('id', 'name'));
}
use this
The all method takes in an array of column names as a parameter.
If you look at the source, it takes * (which means everything) by default.
https://github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L624-L629
You can pass in the columns that you needed and it should return the results only with the specified columns.
<?php
$groups = Group::with('users')->all(array('first_column', 'third_column'));
Use like this.
<?php
class User extends Eloquent {
public function groups()
{
return $this->belongsToMany('Group')->select(array('id', 'name'));
}
}
class Group extends Eloquent {
public function users()
{
return $this->belongsToMany('User')->select(array('id', 'name'));
}
}
?>
Instead of selecting column in relationship, you can select column as below:
$groups = Group::with('users:id,first_name,last_name')->all();
And when you are selecting column in relationship, make sure that you are selected foreign key of relation table