Join pivot has one array - laravel

I guys, i have 2 relations tables, and when listing all my messages, is shown the pivot columns relation, but i need to show the data has one array, is there a method in eloquent cant make this happen?
I searched and i no that is possible manipulate with collection methods but i wonder if there is another way.
My Model query is:
public function messages()
{
return $this->belongsToMany(Message::class,'message_users')->withPivot('is_read','sent_at');
}
This is how it is:
{
"data": [
{
"id": 4,
"title": "test",
"body": "<p>test</p>",
"pivot": {
"user_id": 1,
"message_id": 4,
"is_read": 0,
"sent_at": "2019-06-05 12:59:11"
}
}
]
}
This is how i want:
{
"data": [
{
"id": 4,
"title": "test",
"body": "<p>test</p>",
"user_id": 1,
"message_id": 4,
"is_read": 0,
"sent_at": "2019-06-05 12:59:11"
}
]
}

You can do next: in User model write toArray() method as
/**
* Convert the model instance to an array.
*
* #return array
*/
public function toArray(): array
{
$attributes = $this->attributesToArray();
$attributes = array_merge($attributes, $this->relationsToArray());
// Detect if there is a pivot value and return that as the default value
if (isset($attributes['pivot'] && isset($attributes['pivot']['user_id']))) {
$attributes['user_id'] = $attributes['pivot']['user_id'];
$attributes['message_id'] = $attributes['pivot']['message_id'];
$attributes['is_read'] = $attributes['pivot']['is_read'];
$attributes['sent_at'] = $attributes['pivot']['sent_at'];
unset($attributes['pivot']);
}
return $attributes;
}

Related

Laravel: Cannot reindex collection's array in eager loading after using unset()

I have following code:
// User.php
public function groups() {
return $this->belongsToMany(
Group::class,
'group_user',
'user_id',
'group_id',
'id'
);
}
// Group.php
public function users() {
return $this->belongsToMany(
User::class,
'group_class',
'group_id',
'user_id',
'id'
);
}
And in routes/web.php
Route::get('/test', function () {
$me = App\User::first();
$group = App\Group::with('users')->first();
foreach ($group->users as $user_index => $user) {
// Show all users (a.k.a members) of this group, except myself
if ($user->id == $me->id) {
unset($group->users[$user_index]);
}
}
return $group;
}):
Result:
{
"id": 1,
"name": "ABC Group",
"users": { // This should be array right?
"1": { // This should be start with 0
"id": 2,
"name": "...",
"email": "...",
},
"2": { // This should be 1
"id": 3,
"name": "...",
"email": "...",
}
}
}
What I have tried:
#1 Put values() in the end of foreach loop, like:
foreach ($group->users as $user_index => $user) {
// Show all users (a.k.a members) of this group, except myself
if ($user->id == $me->id) {
unset($group->users[$user_index]);
}
$group->users->values(); // Not working
}
#2 Put values() after the foreach loop, like:
Route::get('/test', function () {
$me = App\User::first();
$group = App\Group::with('users')->first();
foreach ($group->users as $user_index => $user) {
// Show all users (a.k.a members) of this group, except myself
if ($user->id == $me->id) {
unset($group->users[$user_index]);
}
}
$group->users->values(); // Still not working
return $group;
}):
Expected result:
{
"id": 1,
"name": "ABC Group",
"users": [ // Array
{ // index 0
"id": 2,
"name": "...",
"email": "...",
},
{ // index 1
"id": 3,
"name": "...",
"email": "...",
}
]
}
Q: How to reindex collection array in eager loading after using unset()?
Thanks in advance
You've got a few things to unpack here that might help you.
First, your query returns a Laravel collection of users attached to the single model Group. Laravel has a bit of magic in the background that allows for array notation as well, but probably easiest to think about this as a collection for your purposes. In some cases, you can translate this to an array using Laravel's toArray() method, something like:
$userArray = $group->users->toArray();
For dropping an index, or in this case a user from the Group's users, take a look at the forget() method, which works on the collection object.
However, I think you may wish to come at this from the reverse... Pull the unwanted index(es) in a single query, rather than having to loop through the collection after the fact. Something like this may be of value to you:
$me = App\User::first();
$group = App\Group::with(['users' => function($query) use($me){
$query->where('users.id', '!=', $me->id);
}])->first();
This query will remove the unwanted user from the collection right out of the database, eliminating the need for additional code, which is what I think you were after.
HTH.

Model hide mutators and nested fields

Here is my JSON return
{
"product": {
"id": 1,
"name": "Glory Cookware",
"barcode": "1234567891234",
"local_code_id": 1,
"category_id": 1,
"description": null
},
"calculations": {
"product_quantity_in_warehouses": 1580,
"avgSellingPriceBeforeTax": 901.7085714285713,
"avgSellingPriceAfterTax": 1027.9477714285713,
"creditPriceInWarehousesBeforeTax": 1530828,
"creditPriceInWarehousesAfterTax": 1745143.92
}
}
What I am trying to do:
In the Product.php model I am trying to hide calculations.creditPriceInWarehousesBeforeTax.
What I have tried:
I have tried to use toArray() method in the Product.php mode.
public function toArray()
{
$hidden = ['calculations.creditPriceInWarehousesBeforeTax'];
// Also tried ['creditPriceInWarehousesBeforeTax']
$this -> hidden = $hidden;
return parent::toArray();
}
Expected behavior VS what happened
The JSON result should not return the calculations.creditPriceInWarehousesBeforeTax but what happened is that it return with the JSON.
NOTE
calculations are a mutator that I have created to append it as an attribute.

Try to change data in Laravel's pagination → item but fail, how can I solve it?

I making Restful API with Laravel
I tried to change data in pagination result
My code
$rooms->getCollection()->transform(function ($item) {
if (!$item->last_message) {
$item->last_message = (object) array('updated_at' => ''.$item->updated_at);
}
$item->test0 = $item->id;
$item->test1 = $item->last_message;
$item->test2 = \json_encode($item->last_message);
$item->test3 = \json_encode($item);
return $item;
});
I expected that $item->last_message will equals to item->updated_at
but the result always null
Here is json result
{
"current_page": 1,
"data": [
{
"id": 33,
"user_id": 1,
"seller_id": 3,
"room_id": 1,
"market_id": 1,
"created_at": "2019-05-02 10:23:29",
"updated_at": "2019-05-02 10:23:29",
"last_message": null,
"test0": 33,
"test1": {
"updated_at": "2019-05-02 10:23:29"
},
"test2": "{\"updated_at\":\"2019-05-02 10:23:29\"}",
"test3": "{\"id\":33,\"user_id\":1,\"seller_id\":3,\"room_id\":1,\"market_id\":1,\"created_at\":\"2019-05-02 10:23:29\",\"updated_at\":\"2019-05-02 10:23:29\",\"last_message\":null,\"test0\":33,\"test1\":{\"updated_at\":\"2019-05-02 10:23:29\"},\"test2\":\"{\\\"updated_at\\\":\\\"2019-05-02 10:23:29\\\"}\"}"
}
],
"first_page_url": "http://localhost/v2/users/1/chat-rooms?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "http://localhost/v2/users/1/chat-rooms?page=1",
"next_page_url": null,
"path": "http://localhost/v2/users/1/chat-rooms",
"per_page": 15,
"prev_page_url": null,
"to": 1,
"total": 1
}
Notice that I can get data from $item->last_message to set data to $test1 but when parsing object to JSON the $item->last_message data will be null
How can I solve it, Thank you
As I find in Laravel codes it may have functions that can change result of json_encode() result, so I decided to parse paginated value to json then parse to array object to change data and finally parse back to json for response
My code
// Parse to JSON then parse back to array object
// To make sure the format is correct (Same as Laravel standard)
$json = $rooms->toJson();
$parsedJson = \json_decode($json, true);
$items = $parsedJson['data'];
$count = count($items);
for ($i = 0; $i < $count; $i++)
{
$item = $items[$i];
if (!$item['last_message'])
{
$item['last_message'] = array(
'message' => 'Has joined the chat', /* TODO: may change this message later */
'created_at' => $item['created_at'],
'updated_at' => $item['updated_at']);
}
$parsedJson['data'][$i] = $item;
}
from Laravel doc
Unlike most other collection methods, transform modifies the
collection itself. If you wish to create a new collection instead, use
the map method.
So, I would suggest you to use map like so
$rooms->getCollection()->map(function ($item) {
if (!$item->last_message) {
$item->last_message = (object) array('updated_at' => ''.$item->updated_at);
}
$item->test0 = $item->id;
$item->test1 = $item->last_message;
$item->test2 = \json_encode($item->last_message);
$item->test3 = \json_encode($item);
return $item;
});

Laravel, remove null Eloquent object attributes from JSON

Is there an elegant way to remove NULL values from an Eloquent Object? My object is nested with relationships. This particular call can be 1000s of lines long, so my main reason for trying this is to save bandwidth for the user, but server performance is also a consideration.
My code:
$data['locations'] = Location::with('address')->where('user_id', '1')->get();
return Response::json($data);
I experimented with Mutators, but unless I'm mistaken Mutators don't have power over the object key, just the value.
I also tried and failed to use array_filter like these:
Any PHP function that will strip properties of an object that are null?
How to remove empty associative array entries
EDIT As requested,
{
"status": "ok",
"locations": [
{
"id": "1",
"latitude": "12.239107980271",
"longitude": "109.19479025725",
"user_time": "",
"transport": "Bus",
"title1": "",
"title2": "",
"address": {
"town": "Nha Trang",
"country": "Vietnam",
"address": "36-44 Hùng Vương, Lộc Thọ, Nha Trang, Khanh Hoa Province, Vietnam"
},
"altitude": {
"altitude": "10.006237983704"
},
"timezone": {
"offset": "25200"
},
"forecast": {
"icon": "",
"high_temp": "",
"low_temp": ""
}
},
{
"id": "2",
Desired response:
{
"status": "ok",
"locations": [
{
"id": "1",
"latitude": "12.239107980271",
"longitude": "109.19479025725",
"transport": "Bus",
"address": {
"town": "Nha Trang",
"country": "Vietnam",
"address": "36-44 Hùng Vương, Lộc Thọ, Nha Trang, Khanh Hoa Province, Vietnam"
},
"altitude": {
"altitude": "10.006237983704"
},
"timezone": {
"offset": "25200"
}
},
{
"id": "2",
As you can see, I could simply loop through the whole lot and remove any keys - or keys of keys - without values. I was hoping Laravel might provide a neat/fast way of doing the same.
I should add that technically only the latitude and longitude are required fields!
3 possibilities:
Write a response macro which cleans up your json data:
http://laravel.com/docs/responses#response-macros
Extend the Response class and implement your cleanup routine there. See this great tutorial for details how to do this: http://fideloper.com/extend-request-response-laravel
Implement the jsonSerialize method in your model which will be automatically called when your model is converted to json and place your cleanup routines there. You can even go a step further and write your own Collection for your Location model. Depending on your data structure this can make things a little bit easier. A nice tutorial for this purpose can be found here: http://heera.it/extend-laravel-eloquent-collection-object
I personally would prefer option 3.) because the data modifications happens where it should happen - in your model.
But bottom line it really depends which solutions fits best to your project.
First make a trait and add your custom validation then use in your each resource where you need
trait ResourceHelpers
{
/**
* Remove null values from Eloquent api resource
* #param array $data
* #return array
*/
public function removeNullValues(array $data)
{
$filtered_data = [];
foreach ($data as $key => $value) {
// if resource is empty
if ($value instanceof JsonResource and $value->resource === null) {
continue;
}
$filtered_data[$key] = $this->when($value !== null, $value);
}
return $filtered_data;
}
}
Then use it in your resource
class UserResource extends JsonResource
{
use ResourceHelpers;
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return $this->removeNullValues([
"id" => $this->id,
"first_name" => $this->first_name,
"last_name" => $this->last_name,
"phone" => $this->phone,
"email" => $this->email,
"balance" => $this->balance,
'address' => $this->address,
'city' => $this->city,
'state' => $this->state,
'zip_code' => $this->zip_code,
'country' => CountryResource::make($this->whenLoaded('country')),
"joined_at" => $this->created_at,
"updated_at" => $this->updated_at,
]);
}
}
I enhanced the removeNullValues to also ignore empty arrays and be recursive to handle nested arrays. Please check this one.
function removeNullValues(array $data)
{
$filtered_data = [];
foreach ($data as $key => $value) {
if (is_array($value))
{
if (sizeof($value) > 0)
$filtered_data[$key] = $this->removeNullValues($value);
}
else if ($value != null){
$filtered_data[$key] = $value;
}
}
return $filtered_data;
}

Laravel 4.1 remove pivot attributes from response

I am using laravel 4.1 to build an api. I have pivot a table which is working fine. But the response comes with pivot attributes which i don't want. as you will see in my example i have to two tables name: trips and users. I don't want to see pivot table attributes in my response. Here is the example:
[
{
"id": 140,
"name_first": "hasan",
"name_last": "hasibul",
"profile_image": "/assets/images/default-profile-img.png",
"created_at": "2013-09-18 08:19:50",
"last_login": "2013-12-26 11:28:44",
"status": "active",
"last_update": "2013-10-15 13:40:47",
"google_refresh_token": null,
"is_admin": 1,
"updated_at": null,
"pivot": {
"trip_id": 200,
"user_id": 140
}
}
This is my User Model:
public function trips(){
return $this->belongsToMany('Trip');
}
This is my trip model:
public function users(){
return $this->belongsToMany('User');
}
This is my controller:
public function index($tripId)
{
$userCollection = Trip::find($tripId)->users;
return $userCollection;
}
This is my route:
//get all the users belongs to the trip
Route::get('trips/{tripId}/users', array(
'as' => 'trips/users/index',
'uses' => 'TripUserController#index'
));
is there any way i can remove pivot attributes using laravel or i have to use php ?
Use the $hidden property of the model, you can add attributes or relations to it and the pivot is basicly acts as a relation.
class Foo extends Eloquent
{
protected $hidden = array('pivot');
public function bars()
{
return $this->belongsToMany('Bar');
}
}
If you want to remove just any one column from the response, then you can try something like this:
In you Model:
public function toArray()
{
$attributes = $this->attributesToArray();
$attributes = array_merge($attributes, $this->relationsToArray());
unset($attributes['pivot']['user_id']);
return $attributes;
}
This way you will get only attribute required.
You can add it to your "hidden" array. At Model page
protected $hidden = [
'pivot'
];
As mentioned above you can remove the pivot attribute from the response, by adding the following to the related model.
protected $hidden = [
'pivot'
];
Moreover, in case you want to select specific fields from the pivot to be displayed in the related user object you can add this to your controller using Laravel 5.8. This works also when you hide the pivot information with the above code snippet.
public function index(Trip $trip)
{
return $trip->users()->select(['trip_id'])->paginate();
}
and you will receive something objects where the trip_id is added to the user object.
{
"data": [
{
"id": 140,
"trip_id": 200,
"name_first": "hasan",
"name_last": "hasibul",
"profile_image": "/assets/images/default-profile-img.png",
"created_at": "2013-09-18 08:19:50",
"last_login": "2013-12-26 11:28:44",
"status": "active",
"last_update": "2013-10-15 13:40:47",
"google_refresh_token": null,
"is_admin": 1,
"updated_at": null,
"pivot": {
"trip_id": 200,
"user_id": 140
}
}
]
}

Resources