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
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 currently trying to use Laravel Relationships to access my achievements Model using User model, I use this relationship code:
public function achievements()
{
return $this->hasMany('App\Models\User\Achievement');
}
I can easily make some eloquent queries, however I can't access any method that I created there, I can't access this method:
class Achievement extends Model
{
public function achievementsAvailableToClaim(): int
{
// Not an eloquent query
}
}
Using the following code:
Auth::user()->achievements()->achievementsAvailableToClaim();
I believe that I am using this Laravel function in the wrong way, because of that I tried something else without using relationship:
public function achievements()
{
return new \App\Models\User\Achievement;
}
But that would have a performance problem, because would I be creating a new class instance every time I use the achievements function inside user model?
What would be the right way of what I'm trying to do?
it's not working because your eloquent relationship is a hasMany so it return a collection. you can not call the related model function from a collection.
you can var dump it on tinker to understand more what i mean.
You can use laravel scopes.Like local scopes allow you to define common sets of constraints that you may easily re-use throughout your application.
In your case you use this like, Define scope in model:
public function scopeAchievementsAvailableToClaim()
{
return $query->where('achivement_avilable', true);
}
And you can use this like :
Auth::user()->achievements()->achievementsAvailableToClaim();
In a small project with Laravel 5.3 and Stripe, I am trying to force create a Subscription on a User through a hasOne relationship:
// User.php
public function subscription() {
return $this->hasOne('App\Subscription');
}
public function subscribe($data) {
return $this->subscription()->forceCreate(
// $data contains some guarded fields;
// create() will simply ignore them...
);
}
However, I get :
Call to undefined method Illuminate\Database\Query\Builder::forceCreate()
Even though forceCreate() is a valid Eloquent method.
Any ideas how I can simulate this behavior? Or should I just save a Subscription manually assigning each field? The complication is that certain fields should be kept guarded, e.g. stripe_id.
EDIT
My quick' n' dirty solution:
// User.php # subscribe($data)
return (new Subscription())
->forceFill([
'user_id' => $this->id,
// $data with sensitive guarded data
])
->save();
I'm sure there is a better way though!
The call to $this->subscriptions() returns an instance of HasMany class, not a Model.
Contrary to create and createMany, there's no forceCreate() implemented in HasOneOrMany class. And thus, there's no first-hand API to use in such situations.
You see? when you call $this->subscriptions()->create(), you're calling the create method on a HasMany instance, not a Model instance. The fact that Model class has a forceCreate method, has nothing to do with this.
You could use the getRelated() method to fetch the related model and then call forceCreate() or unguarded() on that:
public function subscribe($data) {
return $this->subscription()
->getRelated()
->forceCreate($data);
}
But there's a serious downside to this approach; it does not set the relation as we're fetching the model out of the relation. To work around it you might say:
public function subscribe($data)
{
// $data['user_id'] = $this->id;
// Or more generally:
$data[$this->courses()->getPlainForeignKey()] = $this->courses()->getParentKey();
return $this->courses()
->getRelated()
->forceCreate($data);
}
Ehh, seems too hacky, not my approach. I prefer unguarding the underlying model, calling create and then reguarding it. Something along the lines of:
public function subscribe($data)
{
$this->courses()->getRelated()->unguard();
$created = $this->courses()->create($data);
$this->courses()->getRelated()->reguard();
return $created;
}
This way, you don't have to deal with setting the foreign key by hand.
laravel/internals related discussion:
[PROPOSAL] Force create model through a relationship
I have a view that I'm trying to re-use for two different actions to display data from the database. For one of those actions, an Eloquent collection object is passed to the view, and data is retrieved with
#foreach($buildings as $key=>$value)
{!! $value->build_name !!}
Obviously 'build_name' is a column in the table. So far simple..
Now I need this same view to display data that requires a lot of processing and it's not possible to generate an eloquent statement to pass to the view.
In order to re-use the $value->build_name code, I'm assuming I have to still pass an object (model??) to the view.
I have a Building.php Model
class Building extends Model
{
protected $fillable =[
'buildingtype_id',
'build_name',
];
and I'm thinking I could just add public $build_name; to the Building model, but then I should also add a method to set and get the $build_name. So my Building Model will now look like..
class Building extends Model
{
public $build_name;
protected $fillable =[
'buildingtype_id',
'build_name',
];
public function getBuildName () {
return $this->build_name;
}
public function setBuildName ($name) {
$this->build_name = $name;
}
And I can just create the object myself in the controller...
If I do this, is {!! $value->build_name !!} still appropiate for the view? Or should I now be using {!! $value->getBuildName() !!}
Or am I missing a key concept somewhere? I'm still new to Laravel and OOP.
Edit
I just implemented this, and it's not working. If I add the public $build_name attribute to the model, getBuildName does not return anything, however if I remove public $build_name it does... (which would break my attempting to create that object manually)
When you declare public $build_name, this will override (or more precisely, reset) any other field with the same name in the model. So, you'll have to call setBuildName() setter method before you get it.
I just implemented this, and it's not working. If I add the public $build_name attribute to the model, getBuildName does not return anything
That's because you've called the getter method before the setter, so there is nothing (null) set in the public variable $build_name.
Although you haven't quite mentioned why exactly you want to reuse the Eloquent model, but you can achieve your desired purpose with a little tweak on the model's setter methods:
class Building extends Model
{
/* ... */
public function setBuildName ($name) {
$this->build_name = $name;
return $this;
}
}
Notice returning the current object ($this) in case you would want to chain multiple setter methods in one go.
e.g.:
$model->setBuildName('some name')->setBuildHeight(400);
UPDATE
You can use the default eloquent model to serve your purpose (hence, ridding you of making a duplicate class to achieve roughly the same effect).
Now suppose you have your model Building and would like to set it's attributes manually, then, the following operation on the model is still appropriate:
$building = new App\Building();
$building->build_name = 'Some Building Name'; // you can substitute this with your setter method as well
$building->build_height = 110; // assuming you have a column named `build_height` in your model's table
Note that the only difference in what you'd be doing here is:
You DON'T declare public variables at all in the Eloquent model.
You don't call Eloquent's save() method on the model to persist the manually set data (giving it a transient behavior).
The model now is totally eligible to be passed to your view and you can access it's attributes as you would with a regular model:
<!-- in your view -->
{{ $building->build_name }}
{{ $building->build_height }}
As an alternative approach to setting your arbitrary data, you can have a single setter which accepts an array of key value data to be stored in the model (e.g. ['build_name' => 'Building Name', 'build_height' => 110]):
//in class Building
public function setData($data){
$this->build_name = $data['build_name'];
$this->build_height = $data['build_height'];
}
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');