laravel collection merge didn't work as I expected - laravel

I have similar but slightly different Eloquent Model classes named Exam, Type, Custom.
$recent = Exam::orderBy('updated_at', 'desc')->take(3)->get();
$recent = $recent->merge(Type::orderBy('updated_at', 'desc')->take(3)->get());
$recent = $recent->merge(Custom::orderBy('updated_at', 'desc')->take(3)->get());
Above is what I've tried and it doesn't work. Final $recent collection contains Custom items only. Other items like Exam, Type are disappeared.
Currently, I'm replace this part with push method. It works fine but even after saw the source code of merge method, I cannot find the reason why my previous code doesn't work.

Its because your keys are almost similar (as you mentioned in the question). Values of string keys from the original collection will be replaced by the new collection values if key matches. So merge is not a better approach if you want values from three different models having same keys. Like same id from Exam model will be replaced by the Type model id and then it will be replaced by the Custom model id. push is ok as it doesn't replace the matched key values rather push it as a new item in the collection. So use push instead of merge
Laravel Docs

Related

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 Lighthouse: how to delete records that match certain conditions (rather than deleting via primary key)

In Laravel Lighthouse GraphQL, I'd love to be able to delete records that match certain conditions rather than passing just an individual ID.
I get this error:
The #delete directive requires the field deletePostTag to only contain a single argument.
This functionality seems currently unsupported, but if I'm wrong and this is actually supported, please let me know, because this would be the most straightforward approach.
So then my second approach was to try to first run an #find query to retrieve the ID of the record that I want to delete (based on certain fields equaling certain values).
But https://lighthouse-php.com/4.16/api-reference/directives.html#find shows:
type Query {
userById(id: ID! #eq): User #find
}
and does not show how I could provide (instead of the primary key ID) 2 arguments: a foreign key ID, and a string.
How can I most simply accomplish my goal of deleting records that match certain conditions (rather than deleting via primary key)?
I'm not sure about the #delete functionality regarding multiple arguments, but from what you've posted that appears to be unsupported at the moment. Regarding your query, you should instead use something like #all in conjunction with #where which would allow you to filter the collection by as many vars/args as you'd like. If your argument list grows beyond 3 or so, I would take a look at Complex Where Conditions. They have worked very well for my team so far, and allow a lot of filtering flexibility.
Also take a look at the directive's docs stating:
You can also delete multiple models at once. Define a field that takes a list of IDs and returns a collection of the deleted models.
So if you return multiple models you'd like to delete from your query, you may use this approach to delete them all at once.

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

Using a hard-coded set of values in place of a traditional Eloquent model in Laravel

I'd like to create a many-to-many relationship between two things: Notes and Labels. However, I'd like to define the labels themselves in code rather than having them in a database table.
Aside from a notes table to represent the Note model, I expect to have a "pivot" table (labels_notes) with two columns: note_id and label.
So, my question is: How would eager loading, getter, setter and "get notes by label" methods on the Note model work?
Background: The primary reason for wanting the Labels in code rather than as content of a table is that they are a small, fixed set of values; users will not be allowed to modify them. Further, there may need to be special logic in the code around certain labels. I considered storing them in a JSON column on notes, but am concerned about the performance impact when searching for Notes by Label.
The solution I opted for was to use a traditional Eloquent model for Labels (including a dedicated database table), but inject the desired values into it via the migration, and use a string primary key. That way we're able to use Eloquent in it's intended manner rather than fighting against it.
Using a string primary key means we can write logic based around specific Labels without worrying about arbitrary numeric IDs (i.e., "id=news" vs "id=12112"). Note that doing this also requires adding public $incrementing = false; in the Label model class.
Injecting the necessary Labels via migration lets us avoid having an additional setup task when deploying, and also avoids coupling our code with an external process.

Changing the model's attributes - adding or removing attributes

I am working on a MVC3 code first web application and after I showed the first version to my bosses, they suggested they will need a 'spare' (spare like in something that's not yet defined and we will use it just in case we will need it) attribute in the Employee model.
My intention is to find a way to give them the ability to add as many attributes to the models as they will need. Obviously I don't want them to get their hands on the code and modify it, then deploy it again (I know I didn't mention about the database, that will be another problem). I want a solution that has the ability to add new attributes 'on the fly'.
Do any of you had similar requests and if you had what solution did you find/implement?
I haven't had such a request, but I can imagine a way to get what you want.
I assume you use the Entity Framework, because of your tag.
Let's say we have a class Employee that we want to be extendable. We can give this class a dictionary of strings where the key-type is string, too. Then you can easily add more properties to every employee.
For saving this structure to the database you would need two tables. One that holds the employees and one that holds the properties. Where the properties-table has a foreign-key targeting the employee-table.
Or as suggested in this Q&A (EF Code First - Map Dictionary or custom type as an nvarchar): you can save the contents of the dictionary as XML in one column of the employee table.
This is only one suggestion and it would be nice to know how you solved this.

Resources