How to get only one note from relation belongsToMany? - php

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.

Related

Laravel Eloquent getting data from relations

I have Task model. My Task model has some relationships and it currently looks like this:
class Task extends Model
{
use HasFactory;
public $timestamps = false;
public function city()
{
return $this->hasOne(City::class, 'id', 'city_id');
}
public function type()
{
return $this->hasOne(Type::class, 'id', 'type_id');
}
public function note()
{
return $this->hasOne(Note::class, 'id', 'note_id');
}
public function operator()
{
return $this->hasOne(User::class, 'id', 'operator_id');
}
}
Now, in my TasksController I need to get Tasks that match certain criteria, like this:
$tasks = Task::whereCityId($city->id)->whereTypeId($type->id)->get()->toArray();
The problem is that fields named city_id type_id note_id operator_id will get my integer values that they have.
Instead I would like to get certain value from a related Model.
For example:
operator_id should be replaced with username from User table that corresponds to the user id.
An obvious solution to this would be to simply use foreach loop, go through my results and get the data I need and simply create another array with the information replaced, but I am not sure if this is the best idea and perhaps there is something better.
You have to change in your code:
$this->hasOne(ClassName::class, 'id', 'foreign_key');
To
$this->belongsTo(ClassName::class, 'foreign_key', 'id');
because Task's id does not available as foreign key in these tables. These table's id present in task table as foreign key so you have to use belongsTo() relationship to tell script from where these id belongs.
Then access properties like this:
$tasks = Task::with("type", "city", "operator")
->whereCityId($city->id)->whereTypeId($type->id)->get();
foreach($tasks as $task){
echo $task->city->name;
}
first you should fix your relation:
public function city()
{
return $this->hasOne(City::class,'city_id','id');
}
and so one the same error, foreign key in argument order comes before the primary key.
after that you can use addSelect:
$tasks = Task::whereCityId($city->id)->whereTypeId($type->id)
->addSelect(['userName' => User::select('name')
->whereColumn('users.id', 'tasks.operator_id')
->limit(1)])->get()->toArray();
i think this will help better than what you ask.
$tasks = Task::whereCityId($city->id)
->whereTypeId($type->id)
->with('operator')
->get()->toArray();
with('operator') is ORM feature that make you collection to include its relation as collection property. In this case it will convert to array property.
you could access it from your foreach function as
#foreach($task as $key)
$key['operator']['username']
#endforeach
Have a nice day

Laravel 5 – get particular many to many relation based on model and related model ID

I've got Tag and Attendee Eloquent models, they are in many-to-many relation. Pivot table has also two more attributes – value_int and value_string. My Attendee model looks like this:
class Attendee extends Model
{
public $timestamps = false;
protected $fillable = [
'event_id'
];
public function tags() {
return $this->belongsToMany('App\Models\Tag', 'attendee_tag', 'attendee_id', 'tag_id')
->withPivot(['value_string', 'value_int']);
}
public function scoreTagValue($tag_id) {
return $this->tags->where('tag_id', '=', $tag_id)->first();
}
}
What I want is to obtain pivot values based on Attendee model and variable tag_id, so I've written scoreTagValue function, but it always returns null and I don't know why :( I'm calling it this way:
$attendee->scoreTagValue($tag_id). Thanks for your help :)
You need to access the relation, not the property:
public function scoreTagValue($tag_id) {
return $this->tags()->where('tag_id', '=', $tag_id)->first();
}
Also, according to the docs, withPivot() does not take an array, so:
->withPivot('value_string', 'value_int');

use relationship in model accessor in laravel

Suppose I have a Course model like this :
class Course extends Model
{
public $primaryKey = 'course_id';
protected $appends = ['teacher_name'];
public function getTeacherNameAttribute ()
{
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
}
public function teacher ()
{
return $this->belongsTo('App\User', 'teacher', 'user_id');
}
}
And in the other hand there is a User model like this :
class User extends Authenticatable
{
public $primaryKey = 'user_id';
protected $appends = ['full_name'];
public function getFullNameAttribute ()
{
return $this->name . ' ' . $this->family;
}
public function course ()
{
return $this->hasMany('App\Course', 'teacher', 'user_id');
}
}
As you can see there is a hasMany relationship between those.
There is an full_name accessor in User model.
Now I want to add a teacher_name accessor to Course model that uses it's teacher relations and gets full_name of teacher and appends to Course always.
In fact I want whenever call a Course model, it's related teacher name included like other properties.
But every time , when call a Course model , I got this error :
exception 'ErrorException' with message 'Trying to get property of non-object' in D:\wamp\www\lms-api\app\Course.php:166
That refers to this line of Course model :
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
I do not know how can I solve that and what is problem exactly.
Yikes some interesting answers here.
FYI to those coming after me- getFooAttribute() should return the data, and not modify the internal attributes array.
If you set a new value in the attributes array (that doesnt exist in this model's db schema) and then attempt to save the model, you'll hit a query exception.
It's worth reading up the laravel docs on attribute accessors/mutators for more info.
Furthermore, if you need to access a related object from within the model (like in an accessor) you ought to call $related = $this->getRelation('foo'); - note that if the relation isnt loaded (e.g., if you didnt fetch this object/collection with eager loaded relations) then $this->getRelation() could return null, but crucially if it is loaded, it won't run the same query(ies) to fetch the data again. So couple that with if (!$this->relationLoaded('foo')) { $this->loadRelation('foo'); }. You can then interact with the related object/collection as normal.
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
Should be
$this->attributes['teacher_name'] = $this->teacher->full_name;
First thing is that you want to reference the relationship, so loose the brackets (), and because the relationship is belongsTo, you will have one user / teacher returned. So you don't need the first().
We haven't seen your fields but probably you will have to change:
return $this->belongsTo('App\User', 'teacher', 'user_id');
to
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
where foreign_key and other_key are the primary keys that you need to make the join on.
Check this link from the documentation for reference:
https://laravel.com/docs/5.4/eloquent-relationships#one-to-many-inverse
the right way to do this is:
COURSE
public function setTeacherNameAttribute ()
{
$this->attributes['teacher_name'] = $this->teacher->full_name;
}
100% working for me.
I have one to one relationship between Order and Shipment. I have to add the accessor of shipments table column from orders table.
function getOrderNoAttribute()
{
$appendText = "OR100";
if($this->orderShipment()->first()) {
$appendText = $this->orderShipment()->first()->is_shipping === 1 ? "ORE100" : "OR100";
}
return $appendText . $this->attributes['id'];
}
This error is only object data to array use or array data to object data use.
example::
$var->feild insted of $var[feild]
$var[feild] insted of $var->feild
You should use return for accessors . something like this :
public function getTeacherNameAttribute ()
{
return $this->teacher()->first()->full_name ?? '';
}
maybe a course hasn't teacher.

Custom method chaining with Eloquent ORM?

This is my current query:
$cars = Cars::with('brand')->get();
$cars->map(function($cars){
$cars->fullName = $cars->brand->brand." ".$cars->name;
//other manipulation...
return $cars;
});
I want to manipulate my collection in the model so that I can run something like $cars = Cars::with('brand')->getWithBrand();
How can I do this, so I don't have to write map functions for every time I run the query?
In your particular example, you don't need to use map to modify the Collection at all. You can use an Eloquent accessor to define attributes on a Model that don't exist in the database. In your example, you would define the following method on your Cars model:
public function getFullNameAttribute($value)
{
// make sure brand exists first
if ($this->brand) {
return $this->brand->brand.' '.$this->name;
}
// default if brand doesn't exist
return $this->name;
}
By defining that function on your Model, that function will be called whenever you attempt to use the full_name attribute, as shown in the following code:
$car = Cars::with('brand')->first();
// this will echo the result of the getFullNameAttribute method
echo $car->full_name;
Edit
If you would also like this new attribute to automatically show up in your toArray() or toJson() output, you can add the attribute to the $appends property on your Cars model:
class Cars extends Model
{
protected $appends = ['full_name'];
public function getFullNameAttribute($value)
{
// make sure brand exists first
if ($this->brand) {
return $this->brand->brand.' '.$this->name;
}
// default if brand doesn't exist
return $this->name;
}
}
Be aware, however, that your custom attribute depends on a related object. So, if you do something that accidentally calls toArray(), toJson(), __toString(), etc on a Collection of Cars that has not eager loaded the brand relationship, this will cause the N+1 query issue.
For example:
// Bad: N+1 issue because each printed Car will execute a
// separate query to get its brand to output full_name.
echo Cars::get();
// Good: No N+1 issue because all brands are already loaded.
echo Cars::with('brand')->get();

Laravel Eloquent hasOne returns empty

I have two tables named 'works' and 'slides' as well as two classes that extend Eloquent named 'Work' and 'Slide'.
Inside my 'works' table I have a 'id' column, a 'title' column, a 'description' column and a 'image' column and inside my 'slides' table I have a composite key using a foreign key with two columns named 'id' and 'work_id'.
This is how my database relationship looks:
This is the code for my Slide class:
class Slide extends Eloquent {
public $timestamps = false;
public function workId() {
return $this->hasOne('work', 'work_id', 'id');
}
}
And this is the code for my Work class:
class Work extends Eloquent {
public $timestamps = false;
}
Inside my index page I am trying to get a Work object by calling the 'workId()' method inside my Slide object however it returns empty with this code:
$slides = Slide::all();
$works = new \Illuminate\Database\Eloquent\Collection;
foreach ($slides as $slide) {
$works->push($slide->workId());
}
printf($works);
However, if I replace the '$slide->workId()' call with:
Work::find($slide->work_id)
then it finds the row with no problem.
What should I do to get the function to return the Work object rather than calling Work::find()?
Slide belongs to Work because the foreign key to works is inside the slides table. You're also using the incorrect naming for the relationship function and incorrect syntax for the hasOne.
You need to use:
class Slide extends Eloquent {
public $timestamps = false;
public function work() {
return $this->belongsTo('Work');
//or return $this->belongsTo('Work', 'work_id', 'id');
}
}
To get the Work that belongs to a Slide: $slide->work
Note: you use "association()" for belongsTo, not "push()"

Categories