Concept of table relations in Laravel - php

I have two tables:
Cards
Notes
Each Card has multiple Notes. So there is a relation between them like this:
class Card extends Model {
public function notes ()
{
return $this->hasMany(Note::class);
}
}
Ok well, all fine.
Now I need to understand the concept of these two lines:
$card()->$notes()->first();
and
$card()->$notes->first();
What's the difference between them? As you see in the first one $note() is a function and in the second one $note isn't a function. How will they be translated in PHP?
The first one points out to the card table and the second one points out to the notes table, right? or what? Anyway I've stuck to understand the concept of tham.

I don't know about $ before the $notes in your code but if you trying to say something like this.
1- $card->notes()->first();
2- $card->notes->first();
In the code in line 1, first you have a $card model and then you wanted to access all notes() related to that $card, and because of adding () after notes you simply call query builder on notes, show you can perform any other database query function after that, something like where, orderBy, groupBy, ... and any other complicated query on database.
But in the second one you actually get access to a collection of notes related to that $card, we can say that you get all related notes from database and set it into laravel collections and you are no more able to perform database query on notes.
Note: because laravel collections have some methods like where(), groupBy(), whereIn(), sort(), ... you can use them on the second one, but in that case you perform those methods on collections and not database, you already get all results from database

Related

Query More than one relationship from a model with() in Laravel

I need help to query a model base on its relationship:
I have a model called StoreStock, this model is related to a Product model, which has two model relationships, MasterList and Price.
I want to get all storeStock with two Product relationships.
I know i can do something like
StoreStock::all()->with('product.price')
->get()
With this, i can only pick either price or masterlist
Pass array of relationship to with method
StoreStock::with(['product.price','product.masterlist']) ->get()
A little bit explanation here, many of the Laravel methods which support string, also support arrays. You can hover over the specific method and get the intellisense. In your case, it can be written like:
StoreStock::with(['product.price','product.masterlist'])->get()
If you want to run a specific action over any specific relation, you can also write it like this:
StoreStock::with(['product.price' => function(Builder $query) {
// your action required with the query
},'product.masterlist']) ->get()
Hope someone finds this helpful

Laravel 5 - Conditionally append variables

A few words before
I know that you can append variables to model arrays and json representations by using the protected $appends = ["your", "vars", "here"]; array. But imagine the following situation:
The situation
Our use case would be a fictional game or similiar:
Imagine that we have a User model that holds simple information about an (human) user, like the full name, address and so on.
Now, we also have a Faction model that represents the faction/origin/guild/... of this user.
The Faction model is eager-loaded when retrieving users, because the Faction name is wanted almost every time when displaying the user information.
A User also has DailyStatistics, which holds some information about their daily scores (simple points would be enough).
The Clue
Because I want to know the points of the a faction, which is the sum of the user points, I thought about appending a new variable totalPoints.
The getTotalPointsAttribute function would look like this:
function getTotalPointsAttribute(){
return $this->users->sum->getTotalPoints();
}
The problem
Everytime when we retrieve a user now, the eager-loaded faction would also want to calculate the totalPoints attribute. That means, that we have a lot of overhead per user.
The question
Is there a way to avoid situations like this? Can I "conditionally" append variables? Are properties calculated when they are hidden?
I tried to wrap the totalPoints variable in a simple function, instead of an accessor instead. The problem is, that Frontend-Frameworks like VueJS would need access to the totalPoints variable (or to an endpoint to retrieve that value, but this solution is the least favorable).
I met this problem as I wanted to Appends on the fly but don't want this to auto-appends on any other Controller/Models (The other way is produce 2 Models for the same Table, which is difficult to maintain).
Currently I'm maintaining a Laravel 5.4 (Since they refuse to upgrade PHP5.6 to PHP7)
For Laravel 5.4 and below
Just add a closure after completed the query builder get()
->each(function ($items) {
$items->append('TotalPoints');
);
Source of original solutions: laravel-how-to-ignore-an-accessor
$openOrders = Order::open()->has('contents')
->get(['id','date','tableName'])
->each(function ($items) {
$items->append('TotalPoints');
);
Your model still contains the
public function getTotalPointsAttribute()
{
return $this->users->sum->getTotalPoints();
}
Now you can remove/comment out the the appends in your models :
protected $appends = [
'TotalPoints',
];
Alternatively, if you're on Laravel 5.5 and above, you could use the collection magic like so:
$openOrders->each->setAppends(['TotalPoints']);
Laravel 5.5 and above now have a Laravel 5.6 #Appending At Run Time

Laravel model object retrieving relation model with where condition without "get" method

I'm working on octoberCMS(laravel framework), I have a doubt on retrieving relation model on where clause.
I have fetched a clothing_type record from "clothing type" model based on its primary key "id".
$clothing_type = ClothingType::where('id',post('clothingtype_id'))->where('status','Active')->first();
This "clothing type" model is related with "products" model, the relation is => each clothing type hasMany products.
Every thing works fine; Now my business logic has two cases, one is to get all the products of the clothing type and another is to get the first product of the clothing type. So I have used the $clothing_type->products to get all the products and $clothing_type->products->first() to get the first product.
Now I have to apply a condition for both the cases. The condition is that only the product whose status is "Active" should be fetched, hence
$products = $clothing_type->products->where('status','Active'); and$first_product_detail = $products->first();.
Every thing works as expected but how come the products are fetched without "get()" method. $clothing_type->products->where('status','Active')->get(); Since I'm new to relation I want to know how this works or is this a bad way to get records or improper assumption. But every thing works good.
$clothing_type = ClothingType::where('id',post('clothingtype_id'))->where('status','Active')->first();
if(count($clothing_type->products))
{
$products = $clothing_type->products->where('status','Active');
$first_product_detail = $products->first();
}
You are doing it the correct way. When you access the relationship as an attribute Eloquent automatically retrieves the records.
However, if you access the relationship as a method, you get the query itself, to which you can add your filters:
if(count($clothing_type->products))
{
$products = $clothing_type->products()->where('status','Active')->get();
$first_product_detail = $products->first();
}
This would solve your problems
(documentation is over here (see the first item))
Edit: Also note that the first method is not a method of Eloquent, but from Collection, which is pretty powerful!
Edit2:
I misread the part of your question where you want to know HOW this is possible. Both Eloquent and Collections have a where method. I assume you understand the working of the Eloquent one, but the one from Collection is pretty much the same (see documentation on the Collection where here)
I prefer the Eloquent one myself, because that limits the amount of records that is retrieved from the database. But if you need all the products (even the inactive ones) later on in your code, just use the Collection method to filter the active ones out
There is nothing to be afraid of...
first() and where()
are functions of both Illuminate\Database\Query\Builder as well as Illuminate\Support\Collection and all first does is limit the records to take 1 and then give you the first record. When you use Builder a query is made to get 1 record and 1 you use it on a collection, all records are first get() and then the first of those records is returned.
Here,
When you do,
$clothing_type->products, Laravel gives you a collection of products...
So...
$products is an object of Illuminate\Support\Collection
and
$products->first() calls for the first() function in that class.
Documentation on where and first methods of a collection...

laravel Eloquent join and Object-relationship mapping

Ok so i'm kind of newish to eloquent and laravel (not frameworks tho) but i hit a wall here.
I need to perform some queries with conditions on different tables, so the eager load (::with()) is useless as it creates multiples queries.
Fine, let use the join. But in that case, it seems that Laravel/Eloquent just drops the concept of Object-relationship and just return a flat row.
By exemple:
if i set something like
$allInvoicesQuery = Invoice::join('contacts', 'contacts.id', '=', 'invoices.contact_id')->get();
and then looping such as
foreach ($allInvoicesQuery as $oneInvoice) {
... working with fields
}
There is no more concept of $oneInvoice->invoiceFieldName and $oneInvoice->contact->contactFieldName
I have to get the contacts fields directly by $oneInvoice->contactFieldName
On top of that the same named columns will be overwrited (such as id or created_at).
So my questions are:
Am i right assuming there is no solution to this and i must define manually the field in a select to avoid the same name overwritting like
Invoice::select('invoices.created_at as invoice.create, contacts.created_at as contact_create)
In case of multiple joins, it makes the all query building process long and complex. But mainly, it just ruins all the Model relationship work that a framework should brings no?
Is there any more Model relationship oriented solution to work with laravel or within the Eloquent ORM?
Instead of performing this join, you can use Eloquent's relationships in order to achieve this.
In your Invoice model it would be:
public function contact(){
return $this->belongsTo('\App\Contact');
}
And then of course inside of your Contact model:
public function invoices(){
return $this->hasMany('\App\Invoice');
}
If you want to make sure all queries always have these active, then you'd want the following in your models:
protected $with = ['Invoice']
protected $with = ['Contact'];
Finally, with our relationships well defined, we can do the following:
$invoices = Invoice::all();
And then you can do:
foreach($invoices as $invoice)[
$invoice->contact->name;
$invoice->contact->phone;
//etc
}
Which is what I believe you are looking for.
Furthermore, you can find all this and much more in The Eloquent ORM Guide on Laravel's site.
Maybe a bit old, but I've been in the same situation before.
At least in Laravel 5.2 (and up, presumably), the Eloquent relationships that you have defined should still exist. The objects that are returned should be Invoice objects in your case, you could check by dd($allInvoiceQuery); and see what the objects are in the collection. If they are Invoice objects (and you haven't done ->toArray() or something), you can treat them as such.
To force only having the properties in those objects that are related to the Invoice object you can select them with a wildcard: $allInvoicesQuery = Invoice::select('invoices.*')->join('contacts', 'contacts.id', '=', 'invoices.contact_id')->get();, assuming your corresponding table is called invoices.
Hope this helps.

Laravel One to One several columns with same relation

I've been tasked with building a website given a database that's already in place, using laravel. I'm not able to make any changes to the database structure, as the system is currently in production.
There are two tables: Item and Transaction. A Transaction has a predefined number of items available for it [Item1...Item8] listed in their own separate columns.
I know I can specify in the model to do a:
function item1() {
$this->hasOne('App\Item', 'item_key', 'item1');
}
For each of the columns within the transaction model.
Is there an easier way to concatenate the items together and give me an array?
Like...
function items() {
$this->hasOne('App\Item', 'item_key', ['item1','item2'....]);
}
Edit:
I am aware I could pull each item individually through the items() method and create the array myself. Just wondering if Laravel provides a helper for it.

Categories