Laravel 3 Eloquent ORM usage - laravel

I have the following code which works but doesn't seem to follow the laravel eloquent way:
Article::left_join('images', 'articles.id', '=', 'images.article_id')
->join('article_category', 'articles.id', '=', 'article_category.article_id')
->where('article_category.category_id', '=', $category_id)
->get();
I have 4 tables; articles and categories which have a many to many relationship with each other, a pivot table article_category table which holds the article id and category id and an image table which has one to one relationship with an article.
I setup my models as:
class Category extends Eloquent {
public static function get_articles($category_id) {
return static::find($category_id)->has_many_and_belongs_to('Article');
}
class Article extends Eloquent {
public function categories() {
return $this->has_many_and_belongs_to('Category');
}
public function image() {
return $this->has_one('Image');
}
However I can't seem to get all three bits of info together. I can do:
Category::get_articles($current_category)->get();
To get all articles in a given category but I can't seem to get the image for the article, there seems to be nothing I can chain onto? Unless I'm doing it incorrectly? Is there a trick I'm missing?
I even tried the stripped down version from the docs:
foreach (Article::with('image')->get() as $article) {
echo $article->image->foo;
}
However I get an error: Trying to get property of non-object, even though var_dump shows $article->image is an object! Weird.
Thanks

If you have not setup a model for the image table, do that. The ORM needs the model there so it knows what 'Image' refers to.
Can you get the category information using the ::with method or is that troublesome too?

Related

Laravel eloquent for four tables

I'm new to Laravel. I am developing a project. and in this project I have 4 tables related to each other
-Users
-Orders
-OrderParcels
-Situations
When listing the parcels of an order, I want to get the information of that order only once, the user information of that order once again, and list the parcels as a table under it. so far everything ok. but I also want to display the status of the parcels listed in the table as names. I couldn't add the 4th table to the query. do you have a suggestion? I'm putting pictures that explain the structure below.
My current working code is
$orderParcels = Orders::whereId($id)
->with('parcels')
->with('users:id,name')
->first();
and my 'orders' model has method
public function parcels(){
return $this->hasMany(OrderParcels::class);
}
public function users(){
return $this->hasOne(User::class,'id','affixer_id');
}
Note[edit]: I already know how to connect like this
$orderParcels = DB::table('order_parcels as op')
->leftjoin('orders as o','op.orders_id','o.id')
->leftjoin('users as u','o.affixer_id','u.id')
->leftjoin('situations as s','op.status','s.id')
->select('op.*','o.*','u.name','s.situations_name')
->where('op.orders_id',$id)->get();
but this is not working for me, for each parcels record it returns me orders and user info. I want once orders info and once user info.
Laravel provides an elegant way to manage relations between models. In your situation, the first step is to create all relations described in your schema :
1. Model Order
class User extends Model {
public function parcels()
{
return $this->hasMany(OrderParcels::class);
}
public function users()
{
return $this->hasOne(User::class,'id','affixer_id');
}
}
2. Model Parcel
class Parcel extends Model {
public function situations()
{
return $this->hasOne(Situation::class, ...);
}
}
Then, you can retrieve all desired informations simply like this :
// Retrieve all users of an order
$users = $order->users; // You get a Collection of User instances
// Retrieve all parcels of an order
$parcels = $order->parcels; // You get a Collection of User instances
// Retrieve the situation for a parcel
$situations = $parcel->situations // You get Situation instance
How it works ?
When you add a relation on your model, you can retrieve the result of this relation by using the property with the same name of the method. Laravel will automatically provide you those properties ! (e.g: parcels() method in your Order Model will generate $order->parcels property.
To finish, in this situation where you have nested relations (as describe in your schema), you should use with() method of your model to eager load all the nested relation of order model like this :
$orders = Orders::with(['users', 'parcels', 'parcels.situations'])->find($id)
I encourage you to read those stubs of Laravel documentation :
Define model relations
Eager loading
Laravel Collection
Good luck !
Use join to make a perfect relations between tables.
$output = Orders::join('users', 'users.id', '=', 'orders.user_id')
->join('order_parcels', 'order_parcels.id', '=', 'orders.parcel_id')
->join('situations', 'situation.id', '=', 'order_parcels.situation_id')
->select([
'orders.id AS order_id',
'users.id AS user_id',
'order.parcels.id AS parcel_id',
'and so on'
])
->where('some row', '=', 'some row or variable')->get();

Laravel: With and whereHas to filter second relation hasOne

i'm trying to filter the table using "with" and "whereHas" for the relation and have it follow a second second relation.
Is it possible to do it with "with" or would it only be possible with "Joins"?
Ticket >> StatusHistory (Last record) >> StatusName = 'new'
ticket
-id
-name
status_history
- ticket_id
- status_name_id
- timestamps
status_names
- id
- name (new, close, paused)
<?
class Ticket extends Model
{
public function latestStatus()
{
return $this->hasOne(StatusHistory::class, 'ticket_id', 'id')->latest();
}
class StatusHistory extends Model
{
public function statusName()
{
return $this->hasOne(StatusName::class, 'id', 'status_name_id');
}
This usually works well if there is only one Status history record, but if there are more, it returns values that should not be there.
example: ticket_id 1 has in history first status new and them status paused
With this sentence he returned the ticket to me even so he no longer has the last status in "new".
Ticket::with('latestStatus')
->whereHas('latestStatus.statusName', function($q){
$q->where('name', 'new');
})
According to the documentation (https://laravel.com/docs/8.x/eloquent-relationships#constraining-eager-loads) it is possible. It would look like this:
Ticket::with(['latestStatus' => function($q){
$q->where('name', 'new');
}])->get();
So that the subquery is linked to the relation you are trying to load
To access the first relationship you just use:
$ticket = Ticket::find($id);
$ticket->latestStatus
By having a "hasOne" relationship established, this will return the related record, which from what I see also has a hasOne relationship, so you can do the following:
$ticket->latestStatus->statusName
In this way, you are accessing the second relationship and working it as usual.
However, this is not the only way, as Laravel also offers access to chained relationships through the "has-one-through" method, which according to the documentation is defined as:
"...this relationship indicates that the declaring model can be matched with one instance of another model by proceeding through a third model."
class Ticket extends Model{
public function statusName()
{
return $this->hasOneThrough(StatusName::class, StatusHistory::class);
}
}
Take into account that for this you must follow the conventions established by Laravel. I leave here the related links, I am sure they will be very helpful. Greetings.
Relationships: one-to-one
Relationships: has-one-through

Laravel, many-to-many relationship among multiple models

I Have multiple models that have many to many relationship
Here are the models
News Section
Categories
Sub Categories
Approved News
Pending News
Each News Section can have multiple Categories.
Each Category can have multiple Sub Categories.
Each Sub Category can has multiple Approved News and Pending News.
I want to have News with Categories, Sub Categories and Pending / Approve news
and stuff like
Categories with Sub Categories and Approve news
I tried with pivot tables but not able to get results
Models are as follow
News Section
class NewsSection extends Model
{
public function categories()
{
return $this->belongsToMany(Category::class);
}
}
Category
class Category extends Model
{
public function subcats(){
return $this->belongsToMany(SubCategory::class);
}
public function newssections(){
return $this->belongsToMany(NewsSection::class);
}
}
SubCategory
class SubCategory extends Model
{
public function category(){
return $this->belongsTo(Category::class);
}
public function approvednews(){
return $this->belongsToMany(ApprovedNews::class);
}
public function pendingnews(){
return $this->belongsToMany(PendingNews::class);
}
}
ApprovedNews
class ApprovedNews extends Model
{
public function subcategories (){
return $this->belongsToMany(SubCategory::class);
}
}
PendingdNews
class PendingdNewsextends Model
{
public function subcategories (){
return $this->belongsToMany(SubCategory::class);
}
}
Update
This what I have done so far
$news =Category::with('subcats.approvednews')->where('id',1)->get();
I got all the approved news with subcategories and categories
how can i modify this to get specific subcats and approved news per category, if i do this
$news =Category::with('subcats.approvednews')->where('subcats.id',1)->get();
I get an error like id ambiguous
Is it possible to pick and chose items from relation for instance return just 2 subcats and 3 approved news for each subcat of selected category
or
get count of approved news and pending news per subcat and category
Thanks in advance
The error "error like id ambiguous" means that you need to specify the table in your where('id', 1) like where('table.id', 1) so that MySQL knows which id column in which table you mean.
You can constrain the models returned by with like this:
Category::with(['subcats' => function(Builder $query) {
$query->where('id', '=', 1);
}]);
Also you can count relations:
$subcat = SubCategory::withCount(['approvednews']);
$subcat->approvednews_count;
Limiting eager loaded relations is not possible per the docs.
A workaround may be to go the other way round starting from ApprovedNews:
ApprovedNews::whereHas(['subcategories' => function(Builder $query) {
$query->where('id', '=', 1);
}])->limit(10);
I have a few suggestions of how you can get this to work. In your comments, you say you are getting an issue doing the following:
$items=Category::with('subcategory')->where('id',1)->get();
Where is 'subcategory' coming from? By the looks of your model, your relationship between Category and Subcategory is called subcats. So you would need to change it to:
$items=Category::with('subcats')->where('id',1)->get();
And if you dump that out, you should see that you will get the category where the ID is 1, and the subcategories loaded in. A way to test that your relationships are working would be something like this:
$category = Category::find(1);
$subCats = $category->subcats()->get();
dd($subCats);
In your relationships, instead of using SubCategory::class I would suggest trying return $this->belongsToMany('App\SubCategory'); so that the models are definitely connected.
Once you have tested that your relationships between one another work, you can get started on testing that you can go from a->b->c etc.
May be using "Nested Eager Loading" and "scope", you can do something like
$pendings = NewSection::with('categories.subCategories')->pending()->get()
$approved = NewSection::with('categories.subCategories')->approved()->get()
not tested it, but you can try, may be with some modification, you can reach to your goal.
if you want return one collection, you may like to merge it
$approved->merge($pendings);
but, you should avoid it.

How to retrieve multiple relations with multiple tables in laravel eloquent

I'm using Laravel 5.8 to build a babysitting site. I have 4 tables with different relationships as below:
please see this image
The relationships are:
Babysitter->hasMany(session)
Sessions->hasOne(Review)
Sessions->hasOne(Kids)
Sessions->hasOne(Babysitter)
Sessions->hasOne(Parent)
I want to achieve 2 things:
First one
I want to show this result when listing all babysitters. I'm showing this information for each babysitter:
plsease see this image
See here what I couldn't achieve
plsease see this image
This is my code
Sitters::where('Status', 'active')->where('Verified', 1)->get();
Second one
Also, I've tried to show kids name with parent review as shown here:
plsease see this image
This is what i'm using
Sessions::select('Reviews.*', 'Sessions.Parent_id')->join('Reviews', 'Reviews.Session_id', '=', 'Sessions.id')->with('owner')->where('Trainer_id', session('user')->Id)->where('Status', '=', 'complete')->with('owner')->orderBy('Sessions.id', 'DESC')->get();
Here is Session.php Model
public function owner(){
return $this->belongsTo('App\Models\Parents', 'Parent_id');
}
As discussed change the relations:
Babysitter->hasMany(sesstion)
Sessions->hasOne(Review)
Sessions->belongsTo(Kids)
Sessions->belongsTo(Babysitter)
Sessions->belongsTo(Parent)
First one
in Babysitter.php declare the following attributes
class Babysitter extends Model
{
public function reviews()
{
$this->hasManyThrough(Review::class, Session::class);
}
public function getAverageReviewAttribute()
{
return $this->reviews()->avg('Rating');
}
}
Then you just need to call it on the model instance.
$babysitter = Babysitter::first();
return $babysitter->average_review;
Second one
Just use the relation
$babysitter = BabySitter::with(['sessions' => public function ($session) {
$session->with(['review','parent','kids']);
})->where('trainer_id', '=', session('user')->Id) //did not understand this condition
->first();
This assumes you have parent, kids and review relation declared on Session::class. (change the names if needed)
After a few days of searching & testing, this is what worked for me:
Inside (Sitters) Model, put this relation
public function sessions()
{
return $this->hasMany(Sessions::class, 'sitter_id')
->withCount('reviews')
->withCount(['reviews as review_avg' => function($query){
$query->select(DB::raw('AVG(Rating)'));
}]);
}
Also, inside (Sessions) Model, put this relation
public function reviews()
{
return $this->hasOne(Reviews::class, 'Session_id');
}
Now you query like this
return $sitters = Sitters::with('sessions')->get();
I hope this can help someone :)

Is there an Eloquent way of doing a leftjoin in Laravel 5.4

Is there a eloquent way to do a left join in Laravel?
We'd like to get all games and fill in the progress for each one if it exists in user_games.
Right now we've written the following solution, however this isn't eloquent at all, which we like it to be.
public function usergames($user_id) {
return DB::table('games')->leftJoin('user_games', function ($join) use ($user_id) {
$join->on('user_games.game_id', '=', 'games.id')->where('user_games.user_id', '=', $user_id); })->get();
}
DB model:
Thanks in advance!
A way to do this without you actually writing a left/inner join is to use the eloquent relationships.
In your case you will have 2 model classes: User and Game
class User extends Model {
public function games() {
return $this->belongsToMany(App\Game::class);
}
}
Now, you can access the user's games like so:
$user = App\User::find($user_id);
$usergames = $user->games; // Illuminate\Support\Collection
If you want to get a list of users with games, then look into eager loading. That would look something like this:
User::with('games')->get();
This way, Eloquent will know to lazy load the relationship meaning it will only run 2 queries. One to grab the users. and one to grab the games associated with the user, and then make them available for you in the 'games' property of the user object.

Resources