Array with single attribute of many-to-many Eloquent relationship - laravel

I created a model NearMiss that has a many-to-many relationship with a Type model. When there is a GET request for a specific NearMiss, I want an array to be attached to the JSON output that has the name attribute of all Type instances that belong to the NearMiss instance.
I can attach an array of Types to the output, but I want to get rid of all the additional information (such as pivot information).
The GET request /nearmisses/{nearmiss} executes the following method:
public function show($id)
{
try {
$nearmiss = NearMiss::findOrFail($id)->first();
$nearmiss->types->makeHidden(['id', 'created_at', 'updated_at']);
return response()->json($nearmiss);
} catch(ModelNotFoundException $e) {
abort(400, 'Model not found');
}
}
The NearMiss model has the following types relation:
public function types()
{
return $this->belongsToMany('App\Type', 'near_miss_type', 'near_miss_id', 'type_id');
}
Current output:
{
"id": 1,
"location_long": "0.0000000",
"location_lat": "0.0000000",
"employee_id": 1,
"created_at": "2019-02-11 16:38:24",
"updated_at": "2019-02-11 16:38:24",
"types": [
{
"id": 1,
"name": "Brandgevaar"
"created_at": "2019-02-11 16:33:25",
"updated_at": "2019-02-11 16:33:25",
},
{
"id": 2,
"name": "Slipgevaar",
"created_at": "2019-02-11 16:34:12",
"updated_at": "2019-02-11 16:34:12",
}
]
}
I know that I can remove additional attributes (such as id, created_at, updated_at), but that still leaves me with an array of Type instances (with just a single name attribute). When I try flatten() I receive an error that flatten() can't be used on BelongsToMany relationships.
Desired output:
{
"id": 1,
"location_long": "0.0000000",
"location_lat": "0.0000000",
"employee_id": 1,
"created_at": "2019-02-11 16:38:24",
"updated_at": "2019-02-11 16:38:24",
"types": [
"Brandgevaar", "Slipgevaar"
]
}
Can someone help me to get the desired output please.

You could load the types manually and use the pluck() function to only get the name of each type.
$nearmiss->types = $nearmiss->types()->pluck('name');
However I'm not sure if this will interfere with the magic properties from Eloquent.

Related

ApiFilter and GroupFilter purpose and usage

I'm trying to grasp the purpose and usage of api filters, and the doc I find on the net is extremely sparse, including "https://api-platform.com/docs/core/filters".
From what I understand, the typeFilters allow the api user to filter according to a given strategy (e.g don't retrieve data where dates are null), right?
Writing a filter annotation on the class level is equivalent to writing it on every property of the targetted type, right?
I'd like to understand the group filter, but there is not one example of that. What am I supposed to understand from the class annotation * #ApiFilter(GroupFilter::class, arguments={"parameterName"="foobargroups"})? foobargroup is not used anywhere else in the codebase. Not within the DummyCar.php example nor in another class, ang google doesn't find me even one working example.
What I need is a way to tell the api to return only a part of the entity or another. Would groupFilter do? Or is that only used to handle 1-N relationship?
Those are serializer filters.
if you have an Entity like
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Serializer\Filter\GroupFilter;
use ApiPlatform\Core\Serializer\Filter\PropertyFilter;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ApiResource
* #ApiFilter(PropertyFilter::class)
* #ApiFilter(GroupFilter::class)
*/
class User {
/**
* #Groups({"list"})
*/
public $email;
public $firstname;
public $lastname;
/**
* #Groups({"list"})
*/
public $age;
}
When sending a GET request to /users, the collection in JSON-LD for example should look like
{
...
"hydra:members": [
{
"#id": ...,
"#type": ...,
"email": "john-email#dre.ss",
"firstname": "John",
"lastname": "Doe",
"age": 30
},
{
"#id": ...,
"#type": ...,
"email": "jane-email#dre.ss",
"firstname": "Jane",
"lastname": "Doe",
"age": 20
}
]
...
}
When using the property filter, sending a GET request to /users?properties[]=email&properties[]=firstname, the collection would look like
{
...
"hydra:members": [
{
"#id": ...,
"#type": ...,
"email": "john-email#dre.ss",
"firstname": "John"
},
{
"#id": ...,
"#type": ...,
"email": "jane-email#dre.ss",
"firstname": "Jane"
}
]
...
}
When using the group filter, sending a GET request to /users?groups[]=list, the collection would look like
{
...
"hydra:members": [
{
"#id": ...,
"#type": ...,
"email": "john-email#dre.ss",
"age": 30
},
{
"#id": ...,
"#type": ...,
"email": "jane-email#dre.ss",
"age": 20
}
]
...
}
I hope it helps to understand.
Writing a filter annotation on the class level is equivalent to
writing it on every property of the targetted type, right?
This is true for ORM filter, not serializer filter
Finally, using #ApiFilter(GroupFilter::class, arguments={"parameterName"="foobargroups"}) is to allow you to change the query parameter property if for instance you have a "real" property called groups. Then instead of sending the GET request to /users?groups[]=list, you would send /users?foobargroups[]=list

Laravel Eloquents: Check if child relationship has no data, Dont return any data

I have three entities Inbox , Document , Category
inbox has relation with document and document has many categories,
I have an Eloquent query that check relation between inbox and document and return inbox with documents data,
The Problem is:
I want to list all inbox with documents data only if document has no category (if it has one or more categories i dont want list this document with it's inboxes).
So i have this query now, that is not works correctly:
public static function getUserAndJobPositionsInbox($id, $type)
{
return self::with(['documents' => function($q){
$q->has('categories', '==', 0);
}])->where('inboxable_id', $id)
->where('inboxable_type', $type)->get();
}
This query will return inbox data with no documents if the documents has no categories
BUT i don't want inbox data if documents has no categories. just return empty
The result is :
{
"data": {
"user_inbox_documents": null,
"job_positions_documents": [
[
{
"id": 2,
"document_id": 3,
"document_reference": "RPL2p",
"inboxable_id": 3,
"inboxable_type": "App\\Models\\JobPosition",
"path_id": "2",
"created_at": "2018-09-12 13:21:23",
"updated_at": "2018-09-12 13:21:23",
"documents": []
}
],
[
{
"id": 1,
"document_id": 3,
"document_reference": "RPL2p",
"inboxable_id": 2,
"inboxable_type": "App\\Models\\JobPosition",
"path_id": "1",
"created_at": "2018-09-12 13:21:23",
"updated_at": "2018-09-12 13:21:23",
"documents": []
}
]
]
}
}
Thanks in advance.
You can check the existence of nested relationships using dot (.) for example following code (Laravel documentation) gets all the posts that have at least one comment and that comment should have atleast one vote.
// Retrieve all posts that have at least one comment with votes...
$posts = App\Post::has('comments.votes')->get();
So from above example, you can modify you code as follows:
public static function getUserAndJobPositionsInbox($id, $type)
{
return self::has('documents.categories')
->where('inboxable_id', $id)
->where('inboxable_type', $type)->get();
}

Send multiple variable to laravel Api resources

I want to different type of collection..One containing users acros the city and another one containing near by users. I want these from single api hit. is it possible ? If yes then please suggest how to do that.
Waht I did
return ServiceProviderCollection::collection($near_by);
Output:
"data": [
{
"username": "??",
"email": "??",
"rating": 0,
"role_id": 2,
"wallet": "0"
}
],
I want
return ServiceProviderCollection::collection($near_by,$across_city);
expected output:
{
"across_city": {
"data": [
{
"username": "??",
"email": "??",
}
],
},
"near_by": {
"data": [
{
"username": "??",
"email": "??",
}
],
}
}
No, you can't pass 2 objects in Resource. You can do it like this
return [
'across_city' => ServiceProviderCollection::collection($across_city),
'near_by' => ServiceProviderCollection::collection($near_by)
];
Edit: After comment
If you want to show pagination information then you have to create separate controller action and then return ServiceProviderCollection::collection then you will get result with pagination meta information.
create these action in your controller ex. (UserController)
public function acrossCity(){
$acrossCity = User::where('city','test')->paginate(10); //example
return ServiceProviderCollection::collection($acrossCity);
}
public function nearBy(){
$nearBy = User::where('near','1')->paginate(10); //example
return ServiceProviderCollection::collection($nearBy);
}
create routes for these
Route::get('user/acrossCity','UserController#acrossCity');
Route::get('user/nearBy','UserController#nearBy');
Check document https://laravel.com/docs/5.6/eloquent-resources#pagination
Note: when using resource class then name it without Collection. For your case you should name resource as ServiceProviderResource and then when you call its collection then ServiceProviderResource::collection($object) but when returning single object then new ServiceProviderResource($object).
I am currently using Laravel 7 and in my controller I am passing an array of collection objects to resource class
$data = ['quotation' => Quotation::first()];
return new QuotationResource($data);
and in my resource class I can access the data using
public function toArray($request)
{
return [
'quotation' => $this->resource['quotation']
];
}

Laravel Api resource controller json format

I managed to change the format for the json response of the laravel resource controller for APIs, i want to make a key -> value response but i cant find the way to remove the brackets:
the response i get is this:
{
"1": [
{
"updated_at": 1536147154
}
],
"2": [
{
"updated_at": 1536160598
}
]
}
but i want it to be like this:
{
"1":
{
"updated_at": 1536147154
},
"2":
{
"updated_at": 1536160598
}
}
I get the response from an eloquent collection, and then I group it by id, but I don't know how to get rid of the brackets because the values end in an array.
I don't know if I am clear in my question.
Solved it, i removed the groupby and instead, I used the keyBy to assign their id as a key.
Transformers can be used to format json
I used https://github.com/spatie/laravel-fractal to format my json response in controllers

Return a Boolean instead of the whole collection

The Stack model can have "user progress", users can "clear" stacks. And that progress is stored in the Progress model.
Progress model:
public function stack(){
return $this->belongsTo(Stack::class);
}
It would be neat if it was possible to fetch all stacks, and if the user has "cleared" it or not, with a boolean true or false. I don't won't to fetch all data corresponding to the progress. So I have tried setting up a "finished" relation on the Stack model
public function finished(){
return $this->hasOne(Progress::class) ? true : false;
}
But this setup gives me the following error
Call to a member function addEagerConstraints() on boolean.
This is how I call the relationship at the moment
$user = \App\Stack::with(['finished' => function($q){
return $q->where('user_id', auth()->user()->id);
}])->get();
But that returns the whole collection, and that is not necessary. The expected result should be like:
{
"id": 5,
"user_id": 2,
"subject_id": 2,
"name": "tstar igen",
"slug": "tstar-igen",
"description": "asdasd",
"image": null,
"created_at": "2017-10-06 08:27:36",
"updated_at": "2017-10-06 08:27:36",
"finished": true/false
},
So lets say that here is an relation entry in in the progress table for the stack above.
+----+---------+----------+
| id | user_id | stack_id |
+----+---------+----------+
| 1 | 1 | 5 |
+----+---------+----------+
So when fetching the stacks, with relation finish. The finished column should be true or false. So the return from Stack, with relation finished should be like the following. Notice the finished column changed to false on the last stack, because the relation is not present in the progress table.
{
"id": 5,
"user_id": 2,
"subject_id": 2,
"name": "tstar igen",
"slug": "tstar-igen",
"description": "asdasd",
"image": null,
"created_at": "2017-10-06 08:27:36",
"updated_at": "2017-10-06 08:27:36",
"finished": true
},
{
"id": 6,
"user_id": 2,
"subject_id": 2,
"name": "another stack",
"slug": "another-stack",
"description": "This is a test stack for all the stacks out there",
"image": null,
"created_at": "2017-10-06 08:27:36",
"updated_at": "2017-10-06 08:27:36",
"finished": false
},
You can try withCount that counts your related models. But it will return number of related models not boolean. Here is an example:
Stack.php
public function progress(){
return $this->hasOne(Progress::class);
}
then:
\App\Stack::withCount('progress')->get();
Now results have count_progress attribute with number of related 'Progress' models. Because your relation is hasOne it should be 0 or 1.
You are almost done, You have your relationship setup well except for the boolean stuffs, remove it:
public function finished(){
return $this->hasOne(Progress::class);
}
Then when you need to check you can use has or whereHas relationship method:
$stack = \App\Stack::whereHas('finished', function($q){
return $q->where('user_id', auth()->user()->id);
})->get();
Based on your need thats the best I could conceive. I think this should be sufficient.
It would do well to learn how has and whereHas works. But just to summarize: has simply is like whereHas except that it checks if there is at least one model related to the first model, and the other helps you to make more advanced constraints as shown in the example I gave.
Your model
public function progress(){
return $this->hasOne(Progress::class);
}
Your controller
...
$stacks->load('progress');
resource
'finished' => $this->whenLoaded('progress')->isNotEmpty()

Resources