I'm starting my first Laravel project. I set up and tested all the relationships between tables and all of them look fine but one 1-N relationship. I cannot find the error, could you please help me?
Tables:
User:
*id
*username
Feeling:
*id
*name
*user (it points to User:id)
Class User
<?php
class User extends Eloquent {
protected $table = 'User';
public function feelings() {
return $this->hasMany('Feeling', 'user');
}
}
Class Feeling
class Feeling extends Eloquent {
protected $table = 'Feeling';
public function user() {
return $this->belongsTo('User','user');
}
}
If I execute:
$feeling = Feeling::find(1);
echo($feeling->user->username);
I get an error "Trying to get property of non-object".
If I execute
$feeling = Feeling::find(1);
It prints the Json array of the feeling object and if I execute
$feeling = Feeling::find(1);
echo($feeling->user);
It prints "1". So I'm getting the user ID of the database, but not the user object. I would like to get the user object instead.
Thank you very much.
Problem:
Everything is fine but you have used user as your local key and that's why the confusion because, when you call $feeling->user the magic method __get() is called which is available in the Illuminate/Database/Eloquent and it; as given below:
public function __get($key)
{
return $this->getAttribute($key);
}
The getAttribute() method looks several places for the key and sequentially it looks into the Model itself at first and then looks into the relationship array and then at last checks if the key for examaple in your case user exists as a method in the Model which is Feeling in your case, then it gets the related model and finds the key value.
Hence, your Feeling has a field as user so before it goes to the related model it just finds the field in current Model's attributes so it thinks that, user field has been requested for and you get the numeric id instead if related model instance.
Solution:
Solution is pretty simple, change the related field name (user) to anything else, better is user_id but you are free to use anything but make sure that doesn't create any further confusions. So, if you change it to user_id then make changes to your both models, for example:
public function user() {
return $this->belongsTo('User','user_id');
}
But, if you use user_id then you may omit the second argument from both model's relationship method, so you may write:
// Feeling
return $this->belongsTo('User');
// User
return $this->hasMany('Feeling');
Related
I'm trying to use a HasMany relation in a HasOne.
I have following Models:
class Auction extends Model
{
//...
public function bids(): HasMany
{
return $this->hasMany(Bid::class, 'auction_id');
}
public function approvedBids(): HasMany
{
return $this->bids()->approved();
}
public function topBids(): HasMany
{
return $this->approvedBids()->orderByDesc('price')->take(10);
}
public function topBid(): HasOne
{
//return $this->topBids()->firstOfMany(); // Not Working
//return $this->hasOne(Bid:class, 'auction_id)->ofMany('price','max')->approved(); // not working
//return $this->hasOne(Bid:class, 'auction_id)->approved()->ofMany('price','max'); // not working
//return $this->hasOne(Bid::class, 'auction_id')->ofMany('price', 'max'); // working but not as I expecting
}
}
class Bid extends Model
{
//...
public function scopeApproved(Builder $query): Builder
{
return $query->where('state', BidState::STATE_APPROVED);
}
//...
}
As you can see in the source, I'm looking for a way to make a relation that retrieve the Top Bid (ONE BID) from topBids() relation, but I don't know how, and none of my approaches works:
$this->topBids()->firstOfMany(); // Not Working
$this->hasOne(Bid:class, 'auction_id')->ofMany('price','max')->approved(); // not working
$this->hasOne(Bid:class, 'auction_id')->approved()->ofMany('price','max'); // not working
Unfortunately these shouldn't be a relationships
Real question is why are you trying to make these relationships?
Usually you should be using relationships on model to describe how they are correlating together within the database, the rest of the things you should be defining as a scope on a query or a model, or as an attribute.
So, what I'm trying to say is this:
Keep bids as a relationship, as that is actually a relationship to the Bid model
Update approvedBids to be a scope (or an attribute)
Update topBids to be a scope (or an attribute)
Then, you will be able to find top bid easily by doing something like this:
$this->topBids->first() -> if it is an attribute
$this->topBids()->first() -> if it is a scope
This is how you can create a scope: https://laravel.com/docs/9.x/eloquent#local-scopes
In the end, you can even create an attribute that will allow you to retrieve topBid like this:
public function getTopBidAttribute(){
$this->bids()->approved()->orderByDesc('offered_token_price')->first();
}
Then later you can just do $this->topBid.
I think I've found the solution
public function topBid(): HasOne
{
return $this->hasOne(Bid::class, 'auction_id')
->approved()
->orderByDesc('price');
}
You see the problem was in ofMany() function, which creates a huge SQL and I don't know why!
I've returned a HasOne object here, which supports all kinds of query manipulations. Basically HasOne class, tells the main query, to:
Retrieve the first record of the query I've provided.
So if we use orderBy it only provides an order for HasOne's query. and the main query will take cares of the rest and selects the first record.
I'm having an issue with eloquent whereby when I call $unit->assets I am receiving an empty collection. But if I call $unit->assets()->get() - I receive a collection with the correct results.
According to the documentation my relations are defined correctly, which also seems apparent given that I get correct results when calling get() on the relationship object.
class Unit extends Model
{
protected $table = 'organisation_units';
public function assets()
{
return $this->hasMany(MediaElement::class, 'owner_id');
}
}
class MediaElement extends Model
{
protected $table = 'template_elements';
public function owner()
{
return $this->belongsTo(Unit::class, 'owner_id');
}
}
Table structure is
organisation_units
id | name
template_elements
id | owner_id | name | filename
Turns out the issue was the fact that I use "assets" as the relation method name. "assets" must be a reserved or already used keyword within models or something. Will update the post when I find out exactly why.
Also had the same issue with the method names "colors" and "templates".
The real issue here, was that I was storing my organisation unit in the session and when retrieving that from the session, the relations were already loaded.
Calling $model->fresh() before saving to the session sorted out the issue.
Thanks to those who attempted to help.
For followers relation on same User model I used belongsToMany()
public function followers() {
return $this->belongsToMany('App\User', 'followers', 'follow_id', 'user_id');
}
But since I am using this for chat list on load with vue I am on page load passing json_encode(auth()->user()->followers) which works as I needed.
But when I am lets say using only some columns like:
->select(['id', 'name', 'avatar']);
I have additional method for avatar:
public function image() {
return $this->avatar || 'some-default-image';
}
How can I pass that as well for each of many? Without withDefault method..
Try adding this in your User model
class User extends Model {
protected $appends = ['image'];
// other stuff..
}
this will forcefully inject your computed property ie. image in every User model instance but for it work you have to name your method (or create another) like getImageAttribute()and not simply image()
// getter for `image` property in user model object
public function getImageAttribute() {
return $this->avatar || 'some-default-image';
}
What you are looking for is the Accessor function:
Laravel Accessors and Mutators
Basically you define an accessor function in your model:
public function getAvatarAttribute($value)
{
return $value || 'some-default-image';
}
Then when you access the avatar property using ->avatar, the accessor function will get called and you will get the computed value.
====================================================================
The comment has words limit.
You have followers table where each follower is a User. You use relationship to filter all followers which are a group of Users. You wanted getInfo() to be called on each follower so that the additional data is appended to your JSON structure.
In that case, you don't need to filter through each follower and call getInfo() yourself. You use accessor method, put your code in getInfo() into an accessor method, and modify $appends array in your User model, then then JSON data will be automatically appended.
public function getUserInfoAttribute()
{
$userInfo = ... //use logic in your original getInfo method
return $userInfo;
}
Then you add user_info into your User model's $appends array:
protected $appends = ['user_info'];
This way, your user_info will be automatically included when your instance is serialized into JSON.
Like I said in the comment, you should check out:
Appending Values To JSON
for more information.
As to APIs , whether you are using Vue or React or anything, when passing JSON data for your frontend code to consume, you are basically creating apis. FYI:
Eloquent: API Resources
\App\User
class User
public function status() {
return $this->belongsTo('App\UserStatus', 'user_status_id', 'id');
}
\App\UserStatus
class UserStatus
protected $fillable = ['id'];
public function user() {
return $this->hasMany('App\User', 'user_status_id', 'id');
}
I already have the $user object from a simple User::find() query with some fields, then I try to access the status object by lazy loading it with $user->load('status') method.
I'm following the docs, but it seems useless since $user->status still returns null.
public function foo(User $user) {
$user->load('status');
$user->status // returns null
}
What am I doing wrong?
--------- SOLUTION ---------
Actually, to lazy load any relationship, the foreign key value needs to be stored in the model object.
In my find operation, I wasn't querying the user_status_id field. When I added this field into the query, the $user->status statement started to return the UserStatus model.
I don't think this information is written on the Laravel docs, it may be simple, but it took me some time to figure that out.
Actually, to lazy load any relationship, the foreign key value needs to be stored in the model object.
In my find operation, I wasn't querying the user_status_id field. When I added this field into the query, the $user->status statement started to return the UserStatus model.
I don't think this information is written on the Laravel docs, it may be simple, but it took me some time to figure that out.
in status() relation replace the line with
return $this->belongsTo('\App\UserStatus', 'user_status_id');
in user() relation with this
return $this->hasMany('\App\User', 'user_status_id');
Long story short add a '\' before App and remove third parameter since it is not many-to-many relationship.
Also make sure that you actually use Eloquent so add on top of models
namespace App;
use Illuminate\Database\Eloquent\Model;
class MODELNAME extends Model
and assign a table
protected $table = 'model_table';
I was using Psy Shell to run it. For the first argument in belongsTo(), that could just be Classname::class. I encountered the case that I cannot perform belongsTo(), but if you restart your Psy Shell, it worked. It is weird though.
My controller seems to be buggy here:
public function addAdmin($id) {
$this->User->id = $id;
$this->User->set('role','admin');
$this->User->save();
}
So it throws me the error Call to a member function set() on a non-object.
Actually, I want to update the field 'role' in a table column called 'role' and set it to 'admin'.
Can you imagine what's wrong? I've seen many tutorials using this with success but here apparently i'm missing something.
PS: I'm a cakephp newbie :D
Thank you in advance!
The User model isn't loaded.
Try loading it:
public function addAdmin($id) {
$this->loadModel('User'); // here
$this->User->id = $id;
$this->User->set('role','admin');
$this->User->save();
}
You don't have to load a model if you're in the Controller of that Model - but in this case, you're in a different Controller, so you either need to load the model (like above), or access it via an already-loaded associated model.
So, as an example, if you're in the RolesController, you could access the associated 'User' model without having to specifically load it:
$this->Role->User->set('role', 'admin');
Seen your code, your calling User from AdminsController.
Unless you do a
var $uses = array('Admin', 'User');
on the beggining of the controller (don't do it though, it affects performance), it won't recognize your User model. By default AdminsController only has direct access (that is, using $this->Admin) to Admin model, FoosController to Foo model (with $this->Foo), etc.
You can do as #Dave indicates in his answer, but do have in mind, if Admin is related (with hasMany, belongsTo, or any other association) with the User model, then you can access the User model from the AdminsController like this
$this->Admin->User->set(1,2);
And if you have a Foo model associated with User, for example, the same concatenation rule applies, and in your AdminsController you can do
$this->Admin->User->Foo('find')
(A lot of concatenation like this affects performance).
loadModel works for me! I initially tried using a model function that called two different models but instead I created a a model function for each model and then called functions in a single controller.
if ($this->Transaction->saveTrans(PARAMETERS)){
$this->Session->setFlash(__('Transaction Success!'));
$this->loadModel('Creditcard');
if ($this->Creditcard->saveCC(PARAMETERS)){
$this->Session->setFlash(__('Credit Card Success!'));
} else {
$this->Session->setFlash(__('Unsuccessful: The Credit Card was approved but could not be saved. Please try again.'));
}