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
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.
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
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'm starting with Laravel and I have two simple entities that are users and notes, which are related as follows:
User
public function notes()
{
return $this->hasMany('App\Note');
}
Notes
public function user()
{
return $this->belongsTo('App\User');
}
Now within my controller notes with eloquent, I would like to return all the notes that I have, but instead of receiving the id of the user I would like to return the name of this, there something that I have to specify in the relationship of both entities or simply I should adjust something in my query, at the moment I am using this:
$notes = Note::orderBy('id', 'desc')->get();
You can retrieve user name as below:
$notes = Note:: orderBy ('id', 'desc') -> get ();
foreach ($notes as $note)
{
$name = $note->user->name;
}
If you want to pass the whole $note data with the 'user_name' define your own class first. Something like this.
class my_class{
public $data;
public $user_name;
}
You can run a loop just after you get the $note variable and save the data to a new array as this code here.
$notes = Note::orderBy('id', 'desc')->get();
//define an empty Array
$data_arry=array();
foreach($notes as $note)
{
$custom=new my_class();
$name=User::where('u_user_id', $note->n_user_id)->limit(1)->get();
//First 'u_user_id' for column name in user table second 'n_user_id' for column name in note table
$user_name=$name[0]->u_user_name; // 'u_user_name' for column name of stored the name in user table
//pushing data to the object
$custom->data=$note;
$custom->user_name=$user_name;
$data_arry[]=$custom;
}
Now you can Manage data with new Array.
In my controller, I want to be able to call the model Category and give it an array ('dbField' => 'value') and I want to be able to use this array in a where clause.
My Controller:
$categories = new Category(['name' => 'category name']);
My Model:
public function __construct($attributes = [])
{
parent::__construct($attributes);
...
}
But it doesn't seem to work that way, whenever you pass attributes to the model constructor it's only for mass assignement, is there any way to do that ?
The constructor is for filling attributes.
You can try an actual where
$attributes = ['name' => 'blah'];
$findIt = Category::where($attributes)->get(); // or first();
// get the first matched record based on the attributes or return a new instance filled with those attributes
$findItorNot = Category::firstOrNew($attributes);