Conditionally add additional attribute to resource - laravel

I have the following resource being returned:
{
"id": "b93244c5-0c1e-4388-bd61-fd577b5c7c57",
"state": 3,
"createdAt": "2022-08-03T13:58:27.000000Z",
"attendee": {
"id": "5a0f730a-dcc7-4937-9a6a-bb9fe4a9285c",
"about": "...",
"firstName": "Bobby",
"lastName": "Spencer",
"timezone": "Atlantic/Azores",
"username": "at-sed-2748",
"avatar": null,
"fullName": "Bobby Spencer"
}
}
I would like to add email attribute to attendee ONLY if the logged user meets some condition. I assume that I would use additional but for some reason its not working:
class MeetupAttendeeResource extends JsonResource
{
public function toArray($request)
{
/** #var Meetup $meetup */
$meetup = $request->meetup;
/** #var User $loggedUser */
$loggedUser = $request->user();
/** #var MeetupAttendeeService $meetupAttendeeService */
$meetupAttendeeService = app()->make(MeetupAttendeeService::class);
$attendee = (new UserResource($this->attendee));
if ($meetupAttendeeService->canSeeEmail($loggedUser, $meetup)) {
$attendee->additional(['email' => $this->attendee->email]);
}
return [
'id' => $this->id,
'state' => $this->state->value,
'createdAt' => $this->created_at,
'attendee' => $attendee,
'meetup' => new MeetupResource($this->whenLoaded('meetup')),
];
}
}
Why it's not adding email field? I have tried hardcoding it:
if (true) {
$attendee->additional(['email' => $this->attendee->email]);
}
but still, it doesn't get added.

If you check the documentation for adding resources to responses, it says this (emphasis is mine):
Sometimes you may wish to only include certain meta data with a resource response if the resource is the outermost resource being returned. Typically, this includes meta information about the response as a whole.
This section is where the additional() method is mentioned:
You may also add top-level data when constructing resource instances in your route or controller. The additional method, which is available on all resources, accepts an array of data that should be added to the resource response.
So because your resource is nested inside the response, you can't use the additional() method to add the information.
Maybe create the resource with a null value for that property, and only change it as needed.

Related

Laravel API -Send Notifications to android and iOS client apps with Firebase FCM

I am working on a project which uses android and iOS as front and Backend as Laravel APIs.
So I want to send notifications to all users (include android/iOS) when some event happen like New Offer Created/New Stock available.. So to do this is have gone through some google searches and finally knew that the curl approach seems to be deprecated.. so Is there any better Way to integrate firebase FCM into my Laravel project.. if it so How?
I hope someone can answer this..
Thanks in Advance
Fcm for core already answered here How to send Notification to Android from php?
so in laravel you can
create a config file in config folder and name it as firebase.php
<?php
return [
'fcm_url'=>env('FCM_URL'),
'fcm_api_key'=>env('FCM_API_KEY'),
];
and in env file
FCM_URL=https://fcm.googleapis.com/fcm/send
FCM_API_KEY=
and in code you can create Trait class
<?php
namespace App\Traits;
use Illuminate\Support\Facades\Http;
trait Firebase
{
public function firebaseNotification($fcmNotification){
$fcmUrl =config('firebase.fcm_url');
$apiKey=config('firebase.fcm_api_key');
$http=Http::withHeaders([
'Authorization:key'=>$apiKey,
'Content-Type'=>'application/json'
]) ->post($fcmUrl,$fcmNotification);
return $http->json();
}
}
Then you can include this trait any class where you want to call
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Traits\Firebase;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
use Firebase,AuthenticatesUsers;
public function sendNotification(){
$token="";
$notification = [
'title' =>'title',
'body' => 'body of message.',
'icon' =>'myIcon',
'sound' => 'mySound'
];
$extraNotificationData = ["message" => $notification,"moredata" =>'dd'];
$fcmNotification = [
//'registration_ids' => $tokenList, //multple token array
'to' => $token, //single token
'notification' => $notification,
'data' => $extraNotificationData
];
return $this->firebaseNotification($fcmNotification);
}
}
Also i suggest to create events based to better code optimization
Also read
https://firebase.google.com/docs/cloud-messaging/http-server-ref
registration_ids for multiple users
This parameter specifies the recipient of a multicast message, a
message sent to more than one registration token.
The value should be an array of registration tokens to which to send
the multicast message. The array must contain at least 1 and at most
1000 registration tokens. To send a message to a single device, use
the to parameter.
Multicast messages are only allowed using the HTTP JSON format.
For new Version as mentioned in comment by #JEJ
https://firebase.google.com/docs/cloud-messaging/migrate-v1#python_1
So it will be
$http=Http::withHeaders([
'Authorization'=>'Bearer '.$apiKey,
'Content-Type'=>'application/json; UTF-8'
]) ->post($fcmUrl,$fcmNotification);
return $http->json();
and simple notification message for $fcmNotification
{
"message": {
"topic": "news",
"notification": {
"title": "Breaking News",
"body": "New news story available."
},
"data": {
"story_id": "story_12345"
}
}
}
for targeting multiple platforms
{
"message": {
"topic": "news",
"notification": {
"title": "Breaking News",
"body": "New news story available."
},
"data": {
"story_id": "story_12345"
},
"android": {
"notification": {
"click_action": "TOP_STORY_ACTIVITY"
}
},
"apns": {
"payload": {
"aps": {
"category" : "NEW_MESSAGE_CATEGORY"
}
}
}
}
}
Try Laravel FCM package
For your ease, writing steps here...
SETUP
Installation (terminal)
composer require brozot/laravel-fcm
config/app.php
providers
'providers' => [
// ...
LaravelFCM\FCMServiceProvider::class,
]
aliases
'aliases' => [
...
'FCM' => LaravelFCM\Facades\FCM::class,
]
Publish the package config file (terminal)
php artisan vendor:publish --provider="LaravelFCM\FCMServiceProvider"
USAGE
In your Controller,
import libraries
use LaravelFCM\Message\OptionsBuilder;
use LaravelFCM\Message\PayloadDataBuilder;
use LaravelFCM\Message\PayloadNotificationBuilder;
use FCM;
sending Downstream Message to device(s)
$optionBuilder = new OptionsBuilder();
$optionBuilder->setTimeToLive(60*20);
$notificationBuilder = new PayloadNotificationBuilder('my title');
$notificationBuilder->setBody('Hello world')->setSound('default');
$dataBuilder = new PayloadDataBuilder();
$dataBuilder->addData(['a_data' => 'my_data']);
$option = $optionBuilder->build();
$notification = $notificationBuilder->build();
$data = $dataBuilder->build();
$token = "a_registration_from_your_database" /* OR */ [ /* Array of tokens */ ];
$downstreamResponse = FCM::sendTo($token, $option, $notification, $data);
$downstreamResponse->numberSuccess();
$downstreamResponse->numberFailure();
$downstreamResponse->numberModification();
// return Array - you must remove all this tokens in your database
$downstreamResponse->tokensToDelete();
// return Array (key : oldToken, value : new token - you must change the token in your database)
$downstreamResponse->tokensToModify();
// return Array - you should try to resend the message to the tokens in the array
$downstreamResponse->tokensToRetry();
// return Array (key:token, value:error) - in production you should remove from your database the tokens
$downstreamResponse->tokensWithError();

Laravel 5.7: Construct a "Domain path" accessor by using a domains query to get a domain and it's parent ids recursively

This app allows users to nest domains. A domain is just an eloquent model with an id, a string column that is the domain name and a parent_id that links it to another domain as a "child" or "sub-domain". In theory a user could nest as deeply as need be. I'm attempting to construct a domain path accessor that will resemble a url. Something like. ParentMostDomain/NextChildDomain/AnotherChildDomain/ChildMostdomain with each of the parents of the domain just being the string name. On the model that the accessor will live, there is a domain_id stored that is the child-most domain and a relationship to access the full domain model. On the Domain model, I've created parentDomain and parentDomainsRecursive relationships like so:
/**
* Relationship to the domain the Model is in relation to
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function parentDomain()
{
return $this->belongsTo(Domain::class, 'parent_id', 'id');
}
/**
* Recursive Parent Domains
*
* #return \App\Models\Domain
*/
public function parentDomainsRecursive()
{
return $this->parentDomains()->with('parentDomainsRecursive');
}
And the the accessor has access to the domain: $this->domain->parentDomainsRecursive. Each Domain has a name and that is what I would use to construct the domain_path from Parent-most to child-most. This all feels right so far, but for some reason I can't wrap my head around looping through all of these nested domains, started with the child-most. Any thoughts would be hugely appreciated! My accessor (not pretty) so far:
/**
* Domain Path Attribute
*
* #return mixed
*/
public function getDomainPathAttribute()
{
dd($this->domain->parentDomainsRecursive);
$domainPath = '';
foreach ($this->domain->parentDomainsRecursive as $domain) {
if ($domain) {
$domainPath . $domain->domain . '\\';
}
}
return $domainPath . '//' . $this->domain->domain;
}
That dd($this->domain->parentDomainsRecursive); looks something like this:
"domain": {
"id": "451d35ee-4690-45a7-944c-8804ecfb759a",
"parent_id": "762db7de-0d01-4c87-95c5-b7bb52db3e38",
"name": "Fundamental grid-enabled artificialintelligence",
"created_at": "2019-01-16 21:54:53",
"updated_at": "2019-01-16 21:54:53",
"deleted_at": null,
"parent_domains_recursive": {
"id": "762db7de-0d01-4c87-95c5-b7bb52db3e38",
"parent_id": "a441e544-649c-4df5-8b42-2e731b19a85a",
"name": "Reduced modular budgetarymanagement",
"created_at": "2019-01-16 21:54:52",
"updated_at": "2019-01-16 21:54:52",
"deleted_at": null,
"parent_domains_recursive": {
"id": "a441e544-649c-4df5-8b42-2e731b19a85a",
"parent_id": null,
"name": "Multi-lateral eco-centric orchestration",
"created_at": "2019-01-16 21:54:52",
"updated_at": "2019-01-16 21:54:52",
"deleted_at": null,
"parent_domains_recursive": null
}
}
}
The parent-most has a null parent_id. In the above example, the url I am looking to get with this accessor would be Multi-lateral eco-centric orchestration/Reduced modular budgetarymanagement/Fundamental grid-enabled artificialintelligence.
Ended up solving this with a recursive method:
public function getDomainPath($domainId)
{
$domain = Domain::findOrFail($domainId);
if (is_null($domain->parent_id)) {
return $domain->domain;
}
$path = $this->getDomainPath($domain->parent_id) . '/' . $domain->domain;
return $path;
}
I then call the method in the accessor and pass the child-most domain id.

A different way to version api output with laravel?

I'm about to ...
extend my App/Orm/MyModel.php with Http/Json/V1/MyModel.php so I can keep the $appends, $hides, toArray() neatly tucked away in a V1
namespace and prefix some routing for V1
probably do some custom resolvers for route model binding
And I'm thinking ... really? They haven't built this in... what am I missing here? There's gotta be a quick, turn-key for this. I'm interested in knowing how other people are doing this, so please chime in.
Try Resources instead of Models
Have a look at resources:
https://laravel.com/docs/5.7/eloquent-resources
And add your logic to resources so that you display different versions of a model depending on the API version. You can still make use of $appends and $hidden.
With this approach we return a Resource of a model rather than the model itself.
Here is an example of a UserResource for different API versions:
class UserResource extends JsonResource
{
private $apiVersion;
public function __construct($resource, int $apiVersion = 2) {
$this->apiVersion = $apiVersion; // OPTION 1: Pass API version in the constructor
parent::__construct($resource);
}
public function toArray($request): array
{
// OPTION 2: Get API version in the request (ideally header)
// $apiVersion = $request->header('x-api-version', 2);
/** #var User $user */
$user = $this->resource;
return [
'type' => 'user',
'id' => $user->id,
$this->mergeWhen($this->apiVersion < 2, [
'name' => "{$user->first_name} {$user->last_name}",
], [
'name' => [
'first' => $user->first_name,
'last' => $user->last_name
],
]),
'score' => $user->score,
];
}
}
The you can call:
$user = User::find(5);
return new UserResource($user);
If you need a different connection you can do:
$user = User::on('second_db_connection')->find(5);
So V1 API gets:
{
id: 5,
name: "John Smith",
score: 5
}
and V2 API gets:
{
id: 5,
name: {
first: "John",
last: "Smith",
},
score: 5
}
Now if later you wanted to rename score to points in your DB, and in V3 of your API you also wanted to change your JSON output, but maintain backwards compatibility you can do:
$this->mergeWhen($this->apiVersion < 3, [
'score' => $user->points,
], [
'points' => $user->points,
])
Prefix routes
You can easily prefix routes as mentioned here: https://laravel.com/docs/5.7/routing#route-group-prefixes
Route::prefix('v1')->group(function () {
Route::get('users', function () {
// ...
});
});
Explicit Route Model Binding
To do custom route model bindings have a look at: https://laravel.com/docs/5.7/routing#route-model-binding
e.g.
Route::bind('user', function ($value) {
return App\User::where('name', $value)->first() ?? abort(404); // your customer logic
});

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