Laravel: Eloquent relationship from JSON - php

Still practicing in Laravel. At this time, I have the a page controller that finds all the sections available to the corresponding page (simple HTML sections) and includes them in the blade like this:
#foreach(#sections as #section)
#include('sections.' . $section->filename)
Then I have a sections table with data for the corresponding section, as well as columns column with a JSON data inside, that looks like this:
{"header":"Section column 1", "text":"bruh?", "image_id":""},
{"header":"Section column 2", "text":"bruh?", "image_id":""},
{"header":"Section column 3", "text":"bruh?", "image_id":"7"}
As you can see, there is image_id that should call for filename from the images table using Eloquent relationship (I assume it's gonna be One to Many).
Yes, I know that storing JSON inside the table is not the best solution (especially if you want to cross-request data from it), but it is the best I came up with.
So, the question(s) is: how can I get the filename of the image using just ID from a JSON object or is there any different solution, except JSON?
P.S. columns table and JSON format is used for defining some properties, since some of my sections have 3-9 text columns (col-md-3), and each of them has a different header, text and image, so I store them inside JSON.
P.P.S. My JSON format is valid, I just removed some formatting in order to keep this question clear.

Laravel has no native support for JSON relationships.
I've created a package for this: https://github.com/staudenmeir/eloquent-json-relations
class Section extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
protected $casts = [
'columns' => 'json',
];
public function image()
{
return $this->belongsTo(Image::class, 'columns->image_id');
}
}
class Image extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
public function sections()
{
return $this->hasMany(Section::class, 'columns->image_id');
}
}
{{ $section->image->filename }}

Related

One To Many Relationship in Eloquent with JSON field

I'm trying to create a connection between a JSON field in my database and a table which stores music by ID. So, I have a table called "playlists" which has a field called "songs". In this "songs" field I have a array[] of song ID's e.g. [1,2]. I tried the following code to make a relationship between these two tables:
class Playlist extends Model
{
protected $table = 'playlists';
public function songs()
{
return $this->hasMany('App\Music', 'id');
}
}
I used the foreign_key id because of the songs table which has a id field.
The code I used to retrieve the playlist from the controller is as follows:
$playlist = Playlist::find($id)->songs;
print_r($playlist);
Which outputs:
[1,2]
I most probably did something wrong, not understanding the relationships correctly. Could someone explain how this works? I looked up the documentation but did not get any wiser.
Laravel has no native support for JSON relationships.
I created a package for this: https://github.com/staudenmeir/eloquent-json-relations
If you rename the songs column to song_ids, you can define a many-to-many relationship like this:
class Playlist extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
protected $casts = [
'song_ids' => 'json',
];
public function songs()
{
return $this->belongsToJson('App\Music', 'song_ids');
}
}
class Music extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
public function playlists()
{
return $this->hasManyJson('App\Playlist', 'song_ids');
}
}
Although this is a very old post but I will go ahead and drop my own opinion for my future self and fellow googlers.....
So, If I got this question correctly, you are trying to use a JSON field for a relationship query. This issue I have stumbled across a couple of times, at different occasions for different use-cases. With the most recent being for the purpose of saving a couple of Ids belonging to different tables, in a single JSON field on a given table (While I keep pondering on why the Laravel guy won't just add this functionality already! I Know Pivots, Data Normalization etc....But I'm pleading for the 1%). Until I came across this post on Laracast that worked like a charm.
Apologies for the long intro, let me get right into it....
On your Playlist model (in Laravel 8.0 and a few older versions I can't really keep track of) you can do something like so;
public function songs()
{
$related = $this->hasMany(Song::class);
$related->setQuery(
Song::whereIn('id', $this->song_ids)->getQuery()
);
return $related;
}
I have the really good solution for keeping data in column on json format. It help me on previous project online shop
https://scotch.io/tutorials/working-with-json-in-mysql

Laravel 5 get relation data

I've made application with Laravel 5.5 and I use MySQL database. I have 2 tables
people: id, name, homeplanet_id
planets: id, name
And foreign key people.homeplanet_id references planets id
I also have 2 models: Person and Planet, and PersonController.
I can get Person's data in controller by using
Person::find($id)->getAttributes()
but it is like
[
id => 1
name => Name
homeplanet_id => 1
]
How can I get data look like next
[
id => 1
name => Name
homeplanet_id => Planet_name
]
You may need a transformation layer that sits between your Eloquent models and the JSON responses that are actually returned to your application's users.
https://laravel.com/docs/5.5/eloquent-resources
So, if you want to get JSON, use resources. If you want this data format for Blade views, it's a bad idea to transform it. Just work with the data in Blade template:
{{ $person->name }} lives on the planet {{ $person->planet->name }}
Where planet is name of relationship defined in Person model:
public function planet()
{
return $this->belongsTo(Planet::class, 'homeplanet_id')
}
Use with() method,
Something like
$person = Person::where(array())->with('planet')->get(); //You can fetch all related data by passing multiple values for method with()
To print
{{$person->planet->name}} //where planet indicated the relation on person model as follows
Where planet indicated the relation on Person model.
In our case
Getting data
$person = Person::where(array('id'=>$id))->with('planet')->get();
And in Person model do something like following to fetch planet using with() method:
Person extends model{
public function panet(){
//your relation one to one, one to many etc.....whatever
}
}
Try exploring eager loading in Laravel (eloquent) and scoped query too.
Visit the link for Eager loading and everything will be done by the ORM itself.Link

How to index returned array by key with eager loading in Laravel Eloquent?

I'm working on a side project in which we are trying to implement a "like" functionality for user's posts. We are using Laravel's ORM and we would like to use eager loading to make things easier, I'll outline the issue below. Some information first, the Post.php Model contains this:
public function likes()
{
return $this->hasMany('App\Models\PostLike', 'post_id', 'post_id');
}
The PostController.php implementing the API call to load the posts and their likes looked like:
$posts = Post::with("likes")->where("group_id", "=", $group_id)->get();
An example JSON response from the posts API might look like this:
[
{
"post_id":1,
"group_id":1,
"author_id":1,
"text":"Some text here.",
"created_at":"2015-08-13 00:15:08",
"updated_at":"2015-08-13 00:15:08",
"likes":[
{"post_id":1,"user_id":1,"updated_at":"2015-08-14 03:05:48"}
]
}
]
The issue with this is that the "likes" are not indexed by the user_id, so you have to search through the array to determine if a user has liked the post or not, it would be much easier to have it indexed by the unique user_id key. That would yield something like:
[
{
"post_id":1,
"group_id":1,
"author_id":1,
"text":"Some text here.",
"created_at":"2015-08-13 00:15:08",
"updated_at":"2015-08-13 00:15:08",
"likes":{
"1": {"post_id":1,"user_id":1,"updated_at":"2015-08-14 03:05:48"}
}
}
]
So the ultimate question is how do we index the eager loading response by one of the columns it returns?
In order to achieve that, you'll need to add extra post-processing step to your code that would index the array by id. This is very easy using Collection's helper method keyBy().
What you need is a accessor that would load the relation if needed and reindex the array. This accessor can be used in different scenarios, even without eagerly loading the relation, that's why it needs to handle relation loading if needed.
Adding that method to your Post model should do the trick:
public function getLikesAttribute() {
return $this->getRelationValue('likes')->keyBy('user_id');
}
Custom accessors take priority above relation definitions in cases like that, when botu likes() and getLikesAttribute() exist. That's why your accessor will be called when you do $post->likes and will reindex the table.

Mapping data in Laravel to a JSON object

I'm trying to convert data retrieved from my Laravel model to a JSON object as outlined in the Backbone docs.
My problem is that when I encode the data all I get are the public properties and none of the (protected) attributes - the ones I actually want. This is how I go about it:
Controller
$movie = Movie::with('awards.award', 'customAwards.awardCustom', 'cast.person', 'imdb.rottenTomatoes')->find($id);
return View::make('movie')->with(array(
'movie' => $movie
));
View
<script type="text/javascript">
DS.Resources.Movie = {{json_encode($movie)}};
</script>
DS.Resources.Movie output
{
"timestamps":false,
"incrementing":true,
"exists":true
}
If I var_dump $movie I can see the protected attributes like title, year, a cast collection, awards collection etc. How do I access these properties and map them to my JSON object?
In Eloquent, you should use ->toJson() as it correctly only gets the model's attributes, rather than general class properties. Similarly, it'll get your relationships if you used a ->with() I think.
If you don't want certain attributes to come out in the JSON (like password fields) you can specify an array in your class called hidden, see the docs.
You should unprotect them in your model.
Also you should realize you can use ->toJson() on your model instead of json_encode'ing it.

Laravel belongsToMany to return specific columns only

So I have a many to many relationship between Users and Photos via the pivot table user_photo. I use belongsToMany('Photo') in my User model. However the trouble here is that I have a dozen columns in my Photo table most I don't need (especially during a json response). So an example would be:
//Grab user #987's photos:
User::with('photos')->find(987);
//json output:
{
id: 987,
name: "John Appleseed",
photos: {
id: 5435433,
date: ...,
name: 'feelsgoodman.jpg',
....// other columns that I don't need
}
}
Is it possible to modify this method such that Photos model will only return the accepted columns (say specified by an array ['name', 'date'])?
User.php
public function photos()
{
return $this->belongsToMany('Photo');
}
Note: I only want to select specific columns when doing a User->belongsToMany->Photo only. When doing something like Photo::all(), yes I would want all the columns as normal.
EDIT: I've tried Get specific columns using "with()" function in Laravel Eloquent but the columns are still being selected. Also https://github.com/laravel/laravel/issues/2306
You can use belongsToMany with select operation using laravel relationship.
public function photos()
{
return $this->belongsToMany('Photo')->select(array('name', 'date'));
}
Im assuming you have a column named user_id. Then you should be able to do the following:
public function photos()
{
return $this->belongsToMany('Photo')->select(['id', 'user_id', 'date', 'name']);
}
You have to select, the foreign key, else it will have no way of joining it.
Specifying the exact columns you want for the Photos relationship will likely end up biting you in the butt in the future, should your application's needs ever change. A better solution would be to only specify the data you want to return in that particular instance, i.e. the specific JSON response you're delivering.
Option 1: extend/overwrite the toArray() Eloquent function (which is called by toJson()) and change the information returned by it. This will affect every call to these methods, though, so it may end up giving you the same problems as doing select() in the original query.
Option 2: Create a specific method for your JSON response and call it instead of the general toJson(). That method could then do any data building / array modifications necessary to achieve the specific output you need.
Option 3: If you're working with an API or ajax calls in general that need a specific format, consider using a library such as League/Fractal, which is built for just such an occasion. (Phil is also working on a book on building APIs, and it doesn't suck.)

Categories