can i add new key in models? - php

I want to add a key while I'm getting any response of an API using Models in laravel,
Currently, I'm developing an API to get a response with a new one key to add to dynamically.

If I'm not wrong I guess you are trying to create a new field which is not present in table but need to be created dynamically with some logic inside it and you want to pass that field to each and every product of yours.
If so than I guess Laravel Eloquent Accessor will be a best option to use in this case. With the help of Accessor you can create any kind of field and can call it as same as we are calling others fields.
for more reference please take a look at this https://laravel.com/docs/5.7/eloquent-mutators Laravel Documentation.
A sample picked from Laravel Documentation.
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
Now I can use full_name inside my controller or blade files like $user->full_name and than it will give me the concatenated string of first_name and last_name.

Add the attribute to $appends.
class MyModel extends Model {
...
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['extra'];
public function getExtraAttribute(){
return "Extra Value";
}
...
}

first of all in your model add this to add custom field
protected $appends = ['is_favorite'];
public function getIsFavoriteAttribute($value)
{
return $this->name; // whatever you want add here
}
But it problem while you getting other models details while applying relations and select from relation.

Related

Laravel belongs to many with additional methods

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

Laravel pass accessor to vue

On one of my Laravel 5.4 models I've got multiple accessors. Like this:
public function getRevenueAttribute()
{
return $this->calculate()->revenue($this->price, $this->amount);
}
The problem is that when I pass the model to my vue component:
<product :prp-product="{{json_encode($product)}}"></product>
product.revenue does not exists. Is there a way to achieve this? I don't want to calculate these things in vue again.
Thanks a lot.
You'll need to add an appends Attribute to your model see this for more info.
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['revenue'];

How can I append an Eloquent property from a related model without getting the model's contents?

I have 2 eloquent models:
EloquentUser
and
SharedEvents
They are both related by user_id
I'm attempting to set up and appends attribute in the SharedEvents model that will append the full_name of the user with whom the event has been shared.
For the sake of readability, I'm only including the appends components of my class
class SharedEvents extends Model {
protected $appends = ['fullName'];
/**
* #return BelongsTo
*/
public function user() : BelongsTo {
return $this->belongsTo('Path\To\EloquentUser', 'shared_with_id', 'user_id');
}
/**
* #return mixed
*/
public function getFullNameAttribute(){
return $this->user->user_full_name;
}
Unfortunately when I run this I'm getting back both the full name and the entire user model when I only want the full name.
Is there a way to avoid attaching the content of the user model?
It feels like you're trying to make columns from your EloquentUser model first class citizens in your SharedEvent model. You're getting close, but consider...
When working with relationships, this is a good way to be explicit:
Assuming user_full_name is an accessor on your User model:
// EloquentUser.php
// This will automatically add your accessor to every query
// as long as you select the columns the accessor is made
// up of
protected $appends = ['user_full_name'];
/**
* User Full Name Accessor
* #return string
*/
public function getUserFullNameAttribute()
{
return $this->first_name . ' ' . $this->last_name;
}
// SharedEvent.php
/**
* A SharedEvent belongs to an Eloquent User
* #return BelongsTo
*/
public function user() : BelongsTo
{
return $this->belongsTo('Path\To\EloquentUser', 'shared_with_id', 'user_id');
}
// Somewhere in your controller or wherever you want to access the data
$sharedEvents = SharedEvent::with(['user' => function ($query) {
$query->select('user_id', 'first_name', 'last_name');
}])->where(...)->get(['shared_with_id', ...other columns]); // you must select the related column here
This should get you the closest to what you want, but there are a couple of things you should know:
If user_full_name is an accessor, you need to select all of the columns that make up that accessor (as I mention above)
You must select the related keys (user_id in EloquentUser and shared_with_id in SharedEvent)
The $appends is necessary in EloquentUser here because you can't directly add an accessor to your sub query inside the closure.
Try to get comfortable with using a closure as the 2nd argument in your relationships. It's the best way to really be precise as to which columns you're selecting when you're loading relationships — Eloquent makes it really easy to be lazy and just do:
SharedEvent::with('user')->get();
which as you've see will just do a select * on both SharedEvent and your user relationship.
Another thing I've noticed when working with complex queries that use relationships is that you can quickly reach a point where it feels like you're fighting the framework. That's often a sign to consider simplifying ot just using raw SQL. Eloquent is powerful, but is just another tool in your programming tool belt. Remember that you have other tools at your disposal.
I was running into the same problem, and my first idea was to explicitly hide the (entire) associated model when fetching SharedEvent.
In your case, this would look like:
class SharedEvents extends Model {
protected $appends = ['fullName'];
protected $hidden = ['user'];
...
}
I actually decided against this since I am returning a lot of other attached models, so I decided on attaching the entire user, but I tested it and it works.
FYI, I'm not sure if the discrepancy is due to an older version of Laravel, but Eloquent actually expects $appends attributes to be in snake-case (https://laravel.com/docs/5.6/eloquent-serialization#appending-values-to-json):
Note that attribute names are typically referenced in "snake case", even though the accessor is defined using "camel case"
Can you try this?
public function user() : BelongsTo {
return $this->belongsTo('Path\To\EloquentUser', 'shared_with_id', 'user_id')->select('fullName','user_id');
}
After doing some research and experimentation, it doesn't look like there's a way to do this with Eloquent. The solution I ended up going with unfortunately is running an additional query.
/**
* This is inefficient, but I could not find a way to get the full name
* attribute without including all of user.
*
* #return string
*/
public function getFullNameAttribute(){
return EloquentUser::select('user_full_name')
->where('user_id', $this->shared_with_id)
->first()
->user_full_name;
}

How to override result of hasMany relationship?

Imagine I have a couple of simple objects like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function posts()
{
return $this->hasMany("App\Post");
}
}
class Post extends Model
{
public function user()
{
return $this->belongsTo("App\User");
}
}
We'll say the \App\Post object has an database column called jsondata which contains JSON-encoded data. When I want to display the user's posts in a view with that column decoded, I need to do this in the controller:
$posts = Auth::user()->posts()->get();
foreach ($posts as $post) {
$post->jsondata = json_decode($post->jsondata);
}
return view("user.show", ["posts"=>$posts]);
Is there a way to avoid that foreach loop in my controller and do the JSON decoding at a lower level?
I'm sure I could do this in App\User::posts() but that doesn't help other places where I need to display the decoded data. I tried defining App\Post::get() to override the parent method, but it doesn't work because hasMany() doesn't seem to return an instance of the model at all.
It can be done in different places/ways, but I would suggest to use an append for this property in your model if you want this data is always decoded everywhere and every time you retrieve a Post model, or simply a mutator.
see https://laravel.com/docs/master/eloquent-mutators
In your model you can define:
protected $appends = [
'name_of_property'
];
// calculated / mutated field
public function getNameOfPropertyAttribute()
{
return jsondecode($this->jsondata);
}
You then can always access this property with:
$post->name_of_property
Note the conversion from CamelCase to snake_case and the conversion from getNameOfPropertyAttribute > name_of_property. By default you need to respect this convention to get it working automagically.
You can substitute the name_of_property and NameOfProperty with what you want accordingly.
Cheers
Alessandro's answer seemed like the best one; it pointed me to the Laravel documentation on accessors and mutators. But, near the bottom of the page is an even easier method: attribute casting.
In typical Laravel fashion, they don't actually list all the types you can cast to, but they do mention being able to cast a JSON-formatted database column to an array. A little poking through the source and it turns out you can do the same with an object. So my answer, added to the App\Post controller, is this:
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = ["jsondata"=>"object"];
This automatically does the decode and makes the raw data available. As a bonus, it automatically does a JSON encode when saving!

Laravel accessing collection attributes and model attributes from reusable view

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'];
}

Categories