Using Laravel and Eloquent, return a many to many relationship data into a tree structure - laravel

I am using Laravel 10 with Eloquent.
I have navigations, links, and link_navigation tables.
I am trying to retrieve navigation with its links and have the returned links sorted into its hierarchy using children properties for links that have children.
The hierarchical data is stored in the link_navigation data: (id,parent_id,position,navigation_id,link_id)
In my Navigation model, I have the following method:
public function links()
{
return $this->belongsToMany(Link::class, 'link_navigation')->withPivot('id','parent_id','position')->orderBy('link_navigation.position','ASC');
}
This works great at returning the links in the right order ready for sorting into their hierarchy - however, I am trying to find a method to get this data into its hierarchy at this stage.. Or do I have to sort the data into the tree structure after I've received it?
Any pointers are very much welcome.

Related

Eager loading relations for only a subset of records in a Laravel collection

I'm working on a legacy Zend 1 project that's in the process of migrating to Laravel via the Strangler Pattern, and I'm using Eloquent for some of the database queries (though not all as they're still in the process of being migrated). Because Zend 1 can't work on any version of PHP higher than 7.1, it's stuck on the version of Eloquent from Laravel 5.8 until further notice.
I have one particular table containing navigation items, which has a nullable polymorphic relation for content items that uses a morph map to map the Eloquent models to the item types. One type, static doesn't have an Eloquent model assigned to it since it's just a container for other navigation items. Trying to eager load the relation on any items with the static type breaks it because it's not defined in the morph map, and setting it to null doesn't resolve the issue either.
Right now I load the items with a link type of static in a separate query and merge them with the query that gets the remaining navigation items, but this means there are two separate queries and that's not great for performance. Using a union doesn't resolve the issue because the eager load is a separate query from the one for the navigation items.
I know that it's possible to call load() on a collection after it's already been retrieved via a query in order to eager load a relation, and that it's also possible to constrain the eager load so that, for instance, a WHERE clause can be applied when loading the relation. What I'm trying to find is a means to ensure that the query only attempts to eager load the relation if the item type is not static. In other words, constraining not the query to get the relation, but excluding that record from the query so no attempt is made to retrieve the non-existent relation for that record.
Does anyone know if this is possible? One solution I can think of is to split off the items that aren't static in collections and apply load() there, but that's rather cumbersome and I was hoping there was a more elegant method.

How to create a GraphQL query that returns data from multiple tables/models within one field using Laravel Lighthouse

Im trying to learn GraphQL with Laravel & Lighthouse and have a question Im hoping someone can help me with. I have the following five database tables which are also defined in my Laravel models:
users
books
user_books
book_series
book_copies
I'd like to create a GraphQL endpoint that allows me to get back an array of users and the books they own, where I can pull data from multiple tables into one subfield called "books" like so:
query {
users {
name
books {
title
issue_number
condition
user_notes
}
}
}
To accomplish this in SQL is easy using joins like this:
$users = User::all();
foreach ($users as $user) {
$user['books'] = DB::select('SELECT
book_series.title,
book.issue_number
book_copies.condition,
user_books.notes as user_notes
FROM user_books
JOIN book_copies ON user_books.book_copy_id = book_copies.id
JOIN books ON book_copies.book_id = books.id
JOIN book_series ON books.series_id = book_series.id
WHERE user_books.user_id = ?',[$user['id']])->get();
}
How would I model this in my GraphQL schema file when the object type for "books" is a mashup of properties from four other object types (Book, UserBook, BookCopy, and BookSeries)?
Edit: I was able to get all the data I need by doing a query that looks like this:
users {
name
userBooks {
user_notes
bookCopy {
condition
book {
issue_number
series {
title
}
}
}
}
}
However, as you can see, the data is separated into multiple child objects and is not as ideal as getting it all in one flat "books" object. If anyone knows how I might accomplish getting all the data back in one flat object, Id love to know.
I also noticed that the field names for the relationships need to match up exactly with my controller method names within each model, which are camelCase as per Laravel naming conventions. Except for my other fields are matching the database column names which are lower_underscore. This is a slight nitpick.
Ok, after you edited your question, I will write the answer here, to answer your new questions.
However, as you can see, the data is separated into multiple child objects and is not as ideal as getting it all in one flat "books" object. If anyone knows how I might accomplish getting all the data back in one flat object, Id love to know.
The thing is, that this kind of fetching data is a central idea of GraphQL. You have some types, and these types may have some relations to each other. So you are able to fetch any relations of object, in any depth, even circular.
Lighthouse gives you out of the box support to eloquent relations with batch loading, avoiding the N+1 performance problem.
You also have to keep in mind - every field (literally, EVERY field) in your GraphQL definition is resolved on server. So there is a resolve function for each of the fields. So you are free to write your own resolver for particular fields.
You actually can define a type in your GraphQL, that fits your initial expectation. Then you can define a root Query field e.g. fetchUsers, and create you custom field resolver. You can read in the docs, how it works and how to implement this: https://lighthouse-php.com/5.2/the-basics/fields.html#hello-world
In this field resolver you are able to make your own data fetching, even without using any Laravel/Eloquent API. One thing you have to take care of - return a correct data type with the same structure as your return type in GraphQL for this field.
So to sum up - you have the option to do this. But in my opinion, you have to write more own code, cover it with tests on you own, which turns out in more work for you. I think it is simpler to use build-in directives, like #find, #paginate, #all in combination with relations-directives, which all covered with tests, and don't care about implementation.
I also noticed that the field names for the relationships need to match up exactly with my controller method names within each model, which are camelCase as per Laravel naming conventions.
You probably means methods within Model class, not controller.
Lighthouse provides a #rename directive, which you can use to define different name in GraphQL for your attributes. For the relation directives you can pass an relation parameter, which will be used to fetch the data. so for your example you can use something like this:
type User {
#...
user_books: [Book!]! #hasMany(relation: "userBooks")
}
But in our project we decided to use snak_case also for relations, to keep GraphQL clean with consistent naming convention and less effort

Laravel return two models together?

i have a view which displays some places and i'm fetching this data from my API, the model places have many to many relationship with categories. Is there any way i can fetch both model data from the controller?
Like i have this function to fetch all the places, how can i fetch their categories together?
public function get_places()
{
return Places::all();
}
I know by finding the place with it's id i can do something like $place->categories, but this is not what i'm looking for since i want to fetch and display all of them together in a Vue Component. I'm looking more something like Places::all()->with(Categories::class)
Thanks and regards!
Ok using Places::with('categories')->get(); solved my problem and was exactly what i was looking and thinking of.

Eloquent model setRelation generating array instead of Collection

If you are doing $instance = $model->with('categories')->find($id); and after var_dump($instance->categories) it going to return Collection of categories.
But on the project I'm working on in some heavy queries, we are not using with and getting data with a combination of GROUP_CONCAT and CONCAT, like this:
\DB::raw('GROUP_CONCAT(DISTINCT CONCAT(categories.id, ",,", categories.name) SEPARATOR ";;") as categories'),
And then we are building relations manually parsing result and creating a relationship using $instance->setRelation($relation, $data) but for some reason, it's returning an array of objects instead of Collection.
There are also option to use setRelations() and this method returning Collection but I found if you have bidirectional relations it's creating recursion and working really slow. For example: if in User model we have set $this->hasMany('Comments') and in Comments model we have set return $this->belongsTo('User'); and after when we running setRelations() to manually build relations it is create nesting models with recursion (User->Comments->User and so on).
The third option is to not use setRelation() or setRelations() and just to manually create Collection, populating it and set to model. But in such case, it will not be set as a model relation.
Any suggestions on how to build manually in the right way (to create relation is same way eloquent creating with with).
Group return collection of collection so you have to remove the keys of first collection and for that you can use values function of collection like this
$instance->setRelation('relation', $data->values()->all());
Details https://laravel.com/docs/5.6/collections#method-values

laravel relationship / cannot fetch child table data via parent table

Bit confused on relationships, just don't know which one to be used. Attached picture for you to understand the structure.
Please help me with some basic structure, which relationship should I go with? How will be the model structure, migrations field..here is the basic structure

Resources