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!
Related
I am using laravel-excel.com in my Laravel project.
Since data is not coming from a specific model, I didn't want to create a dummy model class and I generate the excel file directly:
return (new \Illuminate\Support\Collection($data))->downloadExcel(
'informe.xlsx',
null,
false
);
($data is a two-dimensional array with data of the unique data-sheet)
I wonder if there is any way to apply some style on columns (width, font-weight, etc.).
As far as I see it is possible ( like explained here) if I create a model for this excel.
Is it possible without?
It looks like it is impossible to apply styles on excel sheet without having a model.
However, I found a trick that makes it very easy to move my code to a model, I leave it here in case it might help somebody.
The idea is to pass $data array (which I retrieve using a service) as a parameter to the constructor:
Controller:
return Excel::download(new MyExport($data), "$fileName.xlsx");
app/Exports/MyExport.php:
<?php
namespace App\Exports;
use \Illuminate\Support\Collection;
class ActuacionesExport implements FromCollection
{
private $data;
function __construct($data) {
$this->data = $data;
}
public function collection()
{
return new Collection($this->data);
}
}
As you can see, the model does not do anything except of returning a Collection instance, but it permits adding styling, column width, drawings - whatever you need in your excel!
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.
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;
}
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 have several models, all of which need the one before it to be accessed. The examples below describe a similar situation
<?php namespace backend\Models;
use Illuminate\Database\Eloquent\Model;
class Geo_Locations extends Model {
protected $table = 'geo_locations';
protected $fillable = ['id', 'name', 'population', 'square_miles', 'comments'];
public function state(){
return $this->hasMany('backend\Models\Geo_State', 'id', 'state_id');
}
}
The "Geo_State" model
<?php namespace backend\Models;
use Illuminate\Database\Eloquent\Model;
class Geo_State extends Model {
protected $table = 'geo_states';
protected $fillable = ['id', 'name', 'political_obligation', 'comments'];
public function city(){
return $this->hasMany('backend\Models\Geo_City', 'id', 'city_id');
}
}
And then the "Geo_City" model would be
<?php namespace backend\Models;
use Illuminate\Database\Eloquent\Model;
class Geo_City extends Model {
protected $table = 'geo_cities';
protected $fillable = ['id', 'name', 'population', 'comments'];
public function miles(){
return $this->hasMany('backend\Models\Geo_Mile', 'id', 'mile_marker');
}
}
and it can continues but for the sake of ease, I'll stop there. What I would like to accomplish, and I'm sure many others would as well. Would be a way to retrieve all data related to one of the main models. For example, I want to get all the cities and their respective states within a location. What I would like for a result is something like
print_r(json_encode($a->getChildren(), JSON_PRETTY_PRINT));
should print out
{
"Geo_Location":{
"name":"United States",
"population":"300 Some Million",
"comments":"they have a lot of issues",
"Geo_State":{
"name":"Texas",
"population":"Some big number",
"comments":"they have a lot of republicans",
"Geo_City":{
"name":"Dallas",
"population":"A lot",
"comments":"Their food is awesome"
}
}
}
}
I have some of the code in a gist, while it may be poorly documented it's all I've been working with and it's my third attempt at doing this.
Individual calls to the route /api/v1/locations/1 would return all the info for United States but will leave out the Geo_State and below. A call to /api/v1/state/1 will return Texas's information but will leave out the Geo_Location and the Geo_City.
The issue in what I have is that it's not recursive. If I go to the route /api/v1/all it's suppose to return a json array similar to the desired one above, but it's not getting the children.
It sounds like what you want is to load the relationships the model has when you call that model. There are two ways I know you can do that in laravel using eager loading.
First way is when you retrieve the model using the with parameter, you can add the relationship and nested relationships like below.
$geoLocationCollectionObject =
Geo_Location::with('state', 'state.city', 'state.city.miles', 'etc..')
->where('id', $id)
->get();
This will return a collection object - which has a function to return the json (toJson). I'm not sure how it handles the relationships (what your calling children) so you may need to make a custom function to parse the collection object and format it how you've specified.
toJson Function
Second option is similar to the first but instead you add the with parameter (as protected $with array) to the model so that the relationships are loaded everytime you call that model.
Geo_Location class
protected $with = array('state');
Geo_State class
protected $with = array('city');
Geo_City class
protected $with = array('miles');
etc...
Now when you retrieve a Geo_Location collection object (or any of the others) the relationship will already be loaded into the object, I believe it should be recursive so that the city miles will also be called when you get a state model for instance - but haven't tested this myself so you may want to verify that.
I use ChromeLogger to output to the console and check this myself.
If you do end up needing a custom json_encode function you can access the relationship by doing $geoLocation->state to retreive all states and then for each state $state->city etc... See the docs for more info.
Laravel Docs on Eager Loading
I wasn't really sure what was going on in the code you linked, but hopefully this helps some.
So after a bit of fiddling around, turns out that what I am looking for would be
Geo_Locations::with('state.city')->get();
Where Geo_Locations is the base model. The "state" and the "city" would be the relations to the base model. If I were to return the query above, I would end up getting an json array of what I am looking for. Thank you for your help Anoua
(To make this more dynamic I'm going to try to find a way to get all of the names of the functions to automate it all to keep on track with what my goal is. )