In my Project I've 2 tables:
MyAwesomeTable
ID | NAME | SOMEMOREINFO | ...
MySecondTable
ID | MyAwesomeTable_ID | SOMEOTHERDATA
Sorry for the weird formatting, but I do not know how to format tables in Stackoverflow correctly.
In my PHP I've got the following Model.
public class MySecondTable {
...
public function awesomeTable() {
return $this->hasOne('App\Models\MyAwesomeTable', 'id', 'MyAwesomeTable_ID');
}
}
When I'm trying to get the entries of my MySecondTable with the following code, the JSON which is generated contains the MyAwesomeTable_ID AND the resolved awesomeTable.
How can I achieve to only get the resolved awesomeTable, without needing to call something like removeColumns.
$entries = MySecondTable::with(['awesomeTable'])->get();
What the call gives me is:
{
...
'MyAwesomeTable_ID' : 1, // I Don't want this entry
'awesomeTable' : {
'id': 1,
'name' : 'some name',
...
}
}
If you don't want something to appear in the default select-list of a model, add that property to the $hidden array of that model,
public class MySecondTable {
// An array of properties that should not appear
// in the default select-list or JSON output
protected $hidden = ['MyAwesomeTable_ID'];
public function awesomeTable() {
// You can just define relation like this, the if you follow Laravel naming-conventions
return $this->hasOne(MyAwesomeTable::class);
// return $this->hasOne('App\Models\MyAwesomeTable', 'id', 'MyAwesomeTable_ID');
}
}
Laravel documentation
Related
I just have a table that has relation belongsToMany, BUT it was a mistake by developer so I can not change this structure SO I need to get only first(). However, when I take only first it return empty array but I need in object
$animals = Cat::query()->with(['types' => function($query) {
$query->first(); //wrong
}])
So how I can get only first? Because I need to order by this field and I can't because it is array
you can do this in two ways:
1- using hasOne relation:
class Cat {
public function firstType() {
return $this->hasOne(Type::class, 'type_id', 'id')->latest();
}
}
2- using staudenmeir/eloquent-eager-limit
after installing it you can write:
class Cat extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
public function firstType() {
return $this->hasMany(Type::class, 'type_id', 'id')->latest()->limit(1);
}
}
class Type extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
// ......
}
the advantage of HasEagerLimit trait is that you can limit the result not only to one but any number you want ...
now you can write:
$animals = Cat::query()->with('firstType');
You can add a attribute getter and set up into appends attribute. follow bellow example:
class Cat {
protected $appends = ['type'];
public function getTypeAttribute() {
// return the first element from your array of the belongsToMany relationship if it exists
return isset($this->types[0])? $this->types[0] : null;
}
}
That's important to remember this method will bring just one type. If you want to get the same type everytime, you create a diferente table where the cat table has the type_id column.
obs: Sorry for my english, it's still in working progress.
Laravel 5.7. I have 2 Eloquent models: Owner, Cat.
Owner model:
public function cats()
{
return $this->belongsToMany('App\Cat')->withPivot('borrowed');
}
Cat model:
public function owners()
{
return $this->belongsToMany('App\Owner')->withPivot('borrowed');
}
The cat_owner pivot table has these fields:
id | cat_id | owner_id | borrowed
---------------------------------
1 | 3 | 2 | 1
I want my API to return a list of all cats, and if the logged-in user has borrowed this cat, I want the borrowed field to be set to true. This is what I have so far:
Controller:
public function index()
{
return CatResource::collection(Cat::all());
}
CatResource:
public function toArray()
{
$data = ['id' => $this->id, 'borrowed' => false];
$owner = auth()->user();
$ownerCat = $owner->cats()->where('cat_id', $this->id)->first();
if ($ownerCat) {
$data['borrowed'] = $ownerCat->pivot->borrowed == 1 ? true : false;
}
return $data;
}
This works, but it seems wasteful to request the $owner for every cat, e.g. if there's 5000 cats in the database. Is there a more efficient way to do this? I can think of 2 possible ways:
Pass the $owner to the CatResource (requires overriding existing collection resource).
Get this information in the controller first, before passing to the CatResource.
I prefer the second way, but can't see how to do it.
Try Conditional Relationship.
public function toArray($request)
{
return [
'id' => $this->id,
'borrowed' => false,
'borrowed' => $this->whenPivotLoaded('cat_owner', function () {
return $this->owner_id === auth()->id() && $this->pivot->borrowed == 1 ? true : false;
})
];
}
then call return CatResource::collection(Cat::with('owners')->get());
You are right, this does a lot of extra loading. I think you are running into the limitation that you can't know which record form cat_owner you want until you know both the records you're using from the cat and owner table.
For anyone still interested, my solution would be to make a resource that gives you just the pivot values
Since the following returns a collection you canNOT go to the pivot table on it:
/*
* returns a collection
*/
$data['borrowed'] = $this->owners
/*
* So this doesNOT work. Since you can’t get the pivot
* data on a collection, only on a single record
*/
$data['borrowed'] = $this->owners->pivot
You should receive the collection and then you can read the pivot data in the Resource of the owner Record. If this resource is only for the pivot data I would call it something like attributes.
create a new resourse for the attributes, something like:
class CatOwnerAttributeResource extends JsonResource
{
public function toArray($request)
{
return [
'borrowed' => $this->pivot->borrowed,
];
}
}
Then receive the collection like so:
$data = ['id' => $this->id, 'borrowed' => false];
/*
* Get the collection of attributes and grab the first (and only) record.
* NOTE: the filtering is done in the collection, not in the DBM. If there
* is a possibility that the collection of owners who own this cat gets really
* big this is not the way to go!
*/
if ($attributes =
CatOwnerAttributeResource::collection(
$this->owner
->where(‘id’ = $auth->user()->id)
->first()
) {
$data[‘borrowed’] = $attributes->borrowed == 1 ? true : false;
}
return $data;
Couldn’t run this code so please point errors out if you try it and it gives you any, I will adjust.
I a have pivot table and a relation on model Product:
public function product_bodies()
{
return $this->belongsToMany(static::class, 'product_bodies')->withPivot('product_id', 'product_body_id');
}
In the controller when I want to attach data:
$products = ['sadasdasd', 'asdasda', 'asdasd', 'asdasd']; //for column product_body_id
$product = Product::create($request->all());
$product->product_bodies()->attach($products);
I get the error:
General error: 1364 Field 'product_body_id' doesn't have a default
value
If I do this:
public function product_bodies()
{
return $this->belongsToMany(static::class, 'product_bodies', 'product_id', 'product_body_id')->withPivot('product_id', 'product_body_id');
}
Then all works well. But then I can't get pivot data with:
$product->product_bodies;
I get empty items..
How can I fix this problem?
Table product_bodies has 3 columns:
id
product_id
product_body_id
In product_body_id I pass strings.
I have 3 ideas:
You need to replace static::class to '\App\ProductBody' or ProductBody::class
public function product_bodies()
{
return $this->belongsToMany('\App\ProductBody', 'product_bodies')->withPivot('product_id', 'product_body_id');
}
$products must be array of product bodies ids.
$productBodiesIds = [1, 55, 66];
$product->product_bodies()->attach($productBodiesIds);
Maybe, sync instead of attach will be a better solution.
my knowledge on mysql is very basic and now im facing a "complex" (for me) query in which im stuck so thank you in advance if someone could give me some light on this.
I have three tables:
Orders
id | name | comments | ...
OrderLines
id | name | sDate | eDate | comments | ...
OrderLinesStats
id | lineID | date | status | ...
Every day OrderLinesStats is updated via a cron job and gets a new record with actual date, status and other fields so the highest id is the actual data.
Im trying to get that last stats row with a relation in yii2 as follows:
in OrdersLines model:
public function getLastOrdersLinesStats()
{
return $this->hasMany(OrdersLinesStats::className(), ['lineID' => 'id'])
->orderBy(['id'=>SORT_DESC])
->groupBy('lineID');
}
OrdersModel:
public function getOrdersLines()
{
return $this
->hasMany(OrdersLines::className(), ['orderID' => 'id'])
->orderBy(['typeID' => SORT_ASC, 'name' => SORT_ASC])
->with(['lastOrdersLinesStats']);
}
But when I debug the query looks like this:
SELECT * FROM `ordersLinesStats` WHERE `lineID` IN (1873, 1872, 1884, 1883, 1870, 1874, 1876, 1880, 1871, 1877, 1881, 1882, 1885, 1886, 1869, 1875, 1878) GROUP BY `lineID` ORDER BY `id` DESC
and doesnt give me the last stats record for each line... in fact, it gives me the oldest one. Seems that im missing something but i cant find it.
Thanks again
All you need to do is change the getLastOrdersLinesStats() to be as follows:
public function getLastOrdersLinesStats()
{
return $this->hasMany(OrdersLinesStats::className(), ['lineID' => 'id'])
->orderBy(['id'=>SORT_DESC])
->one();
}
This basically returns the last OrderLinesStats row that you want for each Order
You can access this as follows:
if you have an object called myOrder for example
then you can access the row you want as myOrder->lastOrderLinesStats
In OrdersModel add getLastOrderLineStat() method that uses via() junction:
public function getLastOrderLineStat()
{
return $this->hasOne(OrdersLinesStats::className(), ['lineID' => 'id'])
->orderBy(['id'=>SORT_DESC])
->groupBy('lineID')
->via('ordersLines');
}
If $model is an OrdersModel instance, you obtain the last stat row using:
$model->lastOrderLineStat
I am just answering this to be thorough and hopefully help other's who stumble upon this page.
I recommend always including both, hasOne and hasMany. This way, you can pop the top record, or retrieve all of them.
/**
* #return \yii\db\ActiveQuery
*/
public function getUserPlan()
{
return $this->hasOne(UserPlan::className(), ['user_id' => 'id'])
->orderBy(['id' => SORT_DESC])
->one();
}
/**
* #return \yii\db\ActiveQuery
*/
public function getUserPlans()
{
return $this->hasMany(UserPlan::className(), ['user_id' => 'id'])
->orderBy(['id' => SORT_DESC])
->all();
}
hasMany will return an array of ActiveQuery Objects, where hasOne will return just an ActiveQuery Object by itself.
You use them like so (example in UserController on User model):
$model = $this->findOne($id);
or
$model = User::findOne($id);
or
$model = User::find()->where(['id' => $id])->one();
Then grab the relations like so:
$plan = $model->userPlan
or
$plans = $model->userPlans
For userPlan:
$planId = $plan->id;
Handling userPlans:
foreach($plans as $plan) {
$plan->id;
}
After creating model, when I try to get his attributes, i get only fields in database that are filled.
----------------------------------------------
DB: | id | shopID | name | bottleID | capacity |
----------------------------------------------
| 1 | 8 | Cola | 3 | |
----------------------------------------------
In this case I need capacity attribute too, as empty string
public function getDrinkData(Request $request)
{
$drink = Drink::where('shopId', $request->session()->get('shopId'))->first();
if($drink) {
$drink = $drink->attributesToArray();
}
else {
$drink = Drink::firstOrNew(['shopId' => $request->session()->get('shopId')]);
$drink = $drink->attributesToArray(); // i want to get even empty fields
}
return view('shop.drink')->(['drink' => $drink])
}
But for later usage (in view) I need to have all attributes, including empty ones. I know that this code works as it should, but I don't know how to change it to detect all attributes.
The model attributes are populated by reading the data from the database. When using firstOrNew() and a record doesn't exist, it makes a new instance of the model object without reading from the database, so the only attributes it has will be the ones manually assigned. Additionally, since there is no record in the database yet, you can't just re-read the database to get the missing data.
In this case, you can use Schema::getColumnListing($tableName) to get an array of all the columns in the table. With that information, you can create a base array that has all the column names as keys, and null for all the values, and then merge in the values of your Drink object.
public function getDrinkData(Request $request)
{
// firstOrNew will query using attributes, so no need for two queries
$drink = Drink::firstOrNew(['shopId' => $request->session()->get('shopId')]);
// if an existing record was found
if($drink->exists) {
$drink = $drink->attributesToArray();
}
// otherwise a new model instance was instantiated
else {
// get the column names for the table
$columns = Schema::getColumnListing($drink->getTable());
// create array where column names are keys, and values are null
$columns = array_fill_keys($columns, null);
// merge the populated values into the base array
$drink = array_merge($columns, $drink->attributesToArray());
}
return view('shop.drink')->(['drink' => $drink])
}
If you were using firstOrCreate(), then a new record is created when one doesn't exist. Since there a record in the database now, you could simply re-read the record from the database to populated all of the model attributes. For example:
public function getDrinkData(Request $request)
{
$drink = Drink::firstOrCreate(['shopId' => $request->session()->get('shopId')]);
// If it was just created, refresh the model to get all the attributes.
if ($drink->wasRecentlyCreated) {
$drink = $drink->fresh();
}
return view('shop.drink')->(['drink' => $drink->attributesToArray()])
}
What if you were to explicitly declare all the fields you want back.
An example of something I do that is a bit more basic as I'm not using where clauses and just getting all from Request object. I still think this could be helpful to someone out there.
public function getDrinkData(Request $request)
{
// This will only give back the columns/attributes that have data.
// NULL values will be omitted.
//$drink = $request->all();
// However by declaring all the attributes I want I can get back
// columns even if the value is null. Additional filtering can be
// added on if you still want/need to massage the data.
$drink = $request->all([
'id',
'shopID',
'name',
'bottleID',
'capacity'
]);
//...
return view('shop.drink')->(['drink' => $drink])
}
You should add a $fillable array on your eloquent model
protected $fillable = ['name', 'email', 'password'];
make sure to put the name of all the fields you need or you can use "*" to select all.
If you already have that, you can use the ->toArray() method that will get all attributes including the empty ones.
I'm using the same thing for my Post model and it works great with all fields including empty ones.
My model looks like this:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = ["*"];
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Do you need to set the $drink variable again after checking the $drink variable?
you can check the following code?
public function getDrinkData(Request $request)
{
$drink = Drink::where('shopId', $request->session()->get('shopId'))->first();
if(!count($drink)>0) {
$drink = Drink::firstOrNew(['shopId' => $request->session()->get('shopId')]);
}
return view('shop.drink')->(['drink' => $drink]); or
return view('shop.drink',compact('drink'));
}
hope it will help. :) :)
You can hack by overriding attributesToArray method
class Drink extends Model
{
public function attributesToArray()
{
$arr = parent::attributesToArray();
if ( !array_key_exist('capacity', $arr) ) {
$arr['capacity'] = '';
}
return $arr;
}
}
or toArray method
class Drink extends Model
{
public function toArray()
{
$arr = parent::toArray();
if ( !array_key_exist('capacity', $arr) ) {
$arr['capacity'] = '';
}
return $arr;
}
}
then
$dirnk->toArray(); // here capacity will be presented even it null in db