I have a scenario in laravel where in i need to sort a collection after modifying a paginated collection. I have to first get the result of a query, paginate it, then compute distance between two points and then order the collection by distance. However, when i sort the collection, i lose the pagination of the collection and the model changes.
Below is the function when no sorting is applied after the collection is paginated
function testgetrequest(Request $request)
{
//get the list of all service providers
$serviceproviders = Users::from('users')
->join('serviceproviders', 'users.id', '=', 'serviceproviders.user_id')
->join('addresses', 'addresses.service_id', '=', 'serviceproviders.user_id')
->select(
'users.id',
'addresses.latitude',
'addresses.longitude',
'addresses.address',
'users.first_name'
)
->groupBy('users.id')
->paginate(3);
$spcount = 0;
//compute distance for every service provider
foreach ($serviceproviders as $key => $serviceprovider) {
$spcount = $spcount + 1;
$distance = 0;
if ($serviceprovider->address && $serviceprovider->latitude && $serviceprovider->longitude ) {
$add2 = $serviceprovider->address;
$lat2 = $serviceprovider->latitude;
$lng2 = $serviceprovider->longitude;
$distance = (new ServiceProvidersController)->computeDistance($request->lat1, $request->lng1, $lat2, $lng2);
} else {
$add2 = null;
$lat2 = null;
$lng2 = null;
$distance = 0;
}
$serprov_array[$key] = $serviceprovider;
$serprov_array[$key]->address = $add2;
$serprov_array[$key]->latitude = $lat2;
$serprov_array[$key]->longitude = $lng2;
$serprov_array[$key]->distance = $distance;
}
return response()->json(['serviceproviders'=> $serviceproviders ]);
}
Below is the data that is returned from the query
{
"serviceproviders": {
"current_page": 1,
"data": [
{
"id": 43023,
"latitude": "60.1581302",
"longitude": "24.7385724",
"address": "abc 3 CC Espoo Finland",
"first_name": "faheem ashraf",
"distance": 7019.59
},
{
"id": 43030,
"latitude": "60.2054911",
"longitude": "24.6559001",
"address": "askask 6 Espoo Finland",
"first_name": "umar nawaz",
"distance": 7022.1
},
{
"id": 43038,
"latitude": "60.20245",
"longitude": "24.7802269",
"address": "unknownff 14 Espoo Finland",
"first_name": "FinTester1",
"distance": 7025.02
}
],
"first_page_url": "https://myodapi.com/api/custom/testgetrequest?page=1",
"from": 1,
"last_page": 3,
"last_page_url": "https://myodapi.com/api/custom/testgetrequest?page=3",
"next_page_url": "https://myodapi.com/api/custom/testgetrequest?page=2",
"path": "https://myodapi.com/api/custom/testgetrequest",
"per_page": 3,
"prev_page_url": null,
"to": 3,
"total": 7
}
}
This is the model that is used in the mobile app and it works fine. The distance to the model is added after the query is retrieved from the database. Once the distance is computed, I sorted the collection by calling sortbydesc() function on the serviceprovider collection. Below is the piece of code that i added to the end of the above function
$myval = $serviceproviders->sortbydesc('distance');
return response()->json(['serviceproviders'=> $myval ]);
The returned result is changed altogether with removing pagination and adding some kind of index to the sort which has totally changed the model
{
"serviceproviders": {
"2": {
"id": 43038,
"latitude": "60.20245",
"longitude": "24.7802269",
"address": "unknownff 14 Espoo Finland",
"first_name": "FinTester1",
"distance": 7025.02
},
"1": {
"id": 43030,
"latitude": "60.2054911",
"longitude": "24.6559001",
""askask 6 Espoo Finland",
"first_name": "umar nawaz",
"distance": 7022.1
},
"0": {
"id": 43023,
"latitude": "60.1581302",
"longitude": "24.7385724",
"address": "address": "abc 3 CC Espoo Finland",
"first_name": "faheem ashraf",
"distance": 7019.59
}
}
}
Can someone please help me how to sort the collection?
Edited:
Don't know how by I'm wrong because I see groupBy as orderBy so yes you have calculate distance with sql ignore below sorry:
If you not ordering by distance is good to do like that because you will calculate distance only for 3 records instead for all users if you make it with sql.
So here is my solution:
You can convert to toArray() then convert to collection data to have advantage of map and orderBy.
After .... paginate(3); remove the for and map the result with code below:
$serviceproviders = $serviceproviders->toArray();
$serviceproviders['data'] = collect($serviceproviders['data'])->map(function ($serviceprovider, $key) use ($request) {
$serviceprovider['distance'] = 0;
if ($serviceprovider['address'] && $serviceprovider['latitude'] && $serviceprovider['longitude']) {
$serviceprovider['distance'] = (new ServiceProvidersController)->computeDistance($request->lat1, $request->lng1, $serviceprovider['latitude'], $serviceprovider['longitude']);
}
return $serviceprovider;
})
// ->sortByDesc('distance'); // for descending
->sortBy('distance')
->values(); // Edited2 return values to fix the keys
Related
In controller get data from db like this, I want to pass whole of $request to another function in this controller to get price it calculating price based of many things from $request:
$user = Auth::user();
$query = Post::query();
$query
->where('province', '=', $user->province)
->where('city', '=', $user->city);
$customers = $query->get();
$customers['calculator'] = $this->calculator($request); // call function
my problem is it return like this:
{
"0": {
"id": 1,
"hash": "RqH29tkfm1dwGrXp4ZCV",
},
"1": {
"id": 3,
"hash": "RqH29tkfm1dwGsXp4ZCV",
},
"calculator": {
"price": 1
}
}
But I need to use that function for each data, and result should be like this:
{
"0": {
"id": 1,
"hash": "RqH29tkfm1dwGrXp4ZCV",
"calculator": {
"price": 1
}
},
"1": {
"id": 3,
"hash": "RqH29tkfm1dwGsXp4ZCV",
"calculator": {
"price": 1
}
}
}
What you want is to set a calculator key for each item in the $customers collection. So you need to loop over it:
foreach ($customers as $customer) {
$customer->calculator = $this->calculator($request);
}
Notice that since the $customer is a Model you should set the calculator as a property. Internally it will be set to the attributes array.
I have the following which I would like to order alphabetically by the Key i.e first for each array group would be "bname", followed by "created_at".
{
"leads": [
{
"lead_id": 1,
"zoho_lead": null,
"bname": "ABC Limited",
"tname": "ABC",
"source_id": 11,
"industry_id": 1,
"user_id": 1,
"created_at": "2017-09-06 15:54:21",
"updated_at": "2017-09-06 15:54:21",
"user": "Sean McCabe",
"source": "Unknown",
"industry": "None"
},
{
"lead_id": 2,
"zoho_lead": 51186111981,
"bname": "Business Name Limited",
"tname": "Trading Name",
"source_id": 11,
"industry_id": 1,
"user_id": 1,
"created_at": "2017-06-01 12:34:56",
"updated_at": null,
"user": "John Doe",
"source": "Unknown",
"industry": "None"
}
]
}
I'm trying to use ksort like so in the foreach loop:
class LeadController extends Controller
{
use Helpers;
public function index(Lead $leads)
{
$leads = $leads->all();
foreach($leads as $key => $lead){
$lead->user = User::where('id', $lead->user_id)->first()->name;
$lead->source = Source::where('id', $lead->source_id)->first()->name;
$lead->industry = Industry::where('id', $lead->industry_id)->first()->name;
$lead->ksort();
}
return $leads;
}
But I get the following error:
Call to undefined method Illuminate\\Database\\Query\\Builder::ksort()
How do I use this function, or is there a Laravel way of doing this, or a better way altogether?
Thanks.
Managed to get it to return with the Keys in alphabetical order, so below is the solution in-case someone else should require it:
public function index(Lead $leads)
{
$leadOut = Array();
$leads = $leads->all();
foreach($leads as $key => $lead){
$lead->user = User::where('id', $lead->user_id)->first()->name;
$lead->source = Source::where('id', $lead->source_id)->first()->name;
$lead->industry = Industry::where('id', $lead->industry_id)->first()->name;
//Convert to Array
$leadOrder = $lead->toArray();
//Sort as desired
ksort($leadOrder);
//Add to array
$leadOut[] = $leadOrder;
}
return $leadOut;
}
There is likely a cleaner way to do this, but it works for my instance, and perhaps additional answers may be posted that are better.
You could do something like:
return Lead::with('user', 'source', 'industry')->get()->map(function ($lead) {
$item = $lead->toArray();
$item['user'] = $lead->user->name;
$item['source'] = $lead->source->name;
$item['industry'] = $lead->industry->name;
ksort($item);
return $item;
});
This should be much more efficient as it will eager load the relationships rather than make 3 extra queries for each iteration.
I working on an API for my app.
I'm trying to pull items from the database and return them in JSON object,
my items table looks like this:
Items
-id
-name
-description
-price
-currency_id
-company_id
this is how I'm getting the items:
$rows = Company::where('guid',$guid)
->first()
->items()
->orderBy('name', $sort_order);
I want to replace the currency_id with a currency object that contains all the columns of currency table
so my result will be like this:
[
{
'id':'1',
'name':'name',
'description': 'example',
'price':'100',
'currency':{
'id':'1',
'name':'usd',
'symbol': '$'
}
}
]
update:
This is my currencies table:
id
name
symbol
code
Edit 2: The user's problem was more complex than this since there was pagination and search integration with the query. Helped with https://pastebin.com/ppRH3eyx
Edit : I've tested the code. So here.
In Company model
public function items()
{
return $this->hasMany(Item::class);
}
In Item model
public function currency()
{
return $this->belongsTo(Currency::class);
}
Controller logic
$items = Company::with(['items' => function($query) use ($sort_order) {
$query->with('currency')->orderBy('name', $sort_order);
}])
->where('guid', $guid)
->first()
->items;
Result with test data
[
{
"id": 2,
"name": "Toy",
"description": "Random text 2",
"price": 150,
"company_id": 1,
"currency_id": 1,
"currency": {
"id": 1,
"name": "usd",
"symbol": "$",
"code": "USD"
}
},
{
"id": 1,
"name": "Phone",
"description": "Random text",
"price": 100,
"company_id": 1,
"currency_id": 1,
"currency": {
"id": 1,
"name": "usd",
"symbol": "$",
"code": "USD"
}
}
]
Try this.
$rows = Company::with('items.currency')
->where('guid', $guid)
->first()
->items()
->orderBy('name', $sort_order);
Try below
Make one relationship in Item Model
public function currencies() {
return $this->hasMany('App\Currency');
}
then do below in your controller
$row=Items::All()->with('currencies');
I would like to flatten an object. This is what I've got so far:
{
"1": {
"id": 1,
"name": "parent",
"children": {
"4": {
"id": 4,
"name": "child1",
"parent": 1
},
"5": {
"id": 5,
"name": "child2",
"parent": 1
}
}
},
"2":{
"id": 2,
"name": "parent2"
}
}
And this is what I would like to accomplish. So keep the same order but flatten the object:
{
"1": {
"id": 1,
"name": "parent",
},
"4": {
"id": 4,
"name": "child1",
"parent": 1
},
"5": {
"id": 5,
"name": "child2",
"parent": 1
},
"2": {
"id": 2,
"name": "parent2"
}
}
So far I haven't found a solution to this. I've tried a function without much success:
protected function _flattenObject($array)
{
static $flattened = [];
if(is_object($array) && count($array) > 0)
{
foreach ($array as $key => $member) {
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
$this->_flattenObject($member);
}
}
}
return $flattened;
}
The tough part for me is to keep the same order (children below its parent). And the function mentioned above also removes all objects and almost only keeps the keys with its value, so it wasn't a great success at all.
Hopefully somebody over here knows a good solution for this.
By the way, the reason I want such flatten structure is because the system I have to work with, has trouble handling multidimensional arrays and objects. And I still want to display an hierarchy, which is possible with the flatten structure I described, because the objects actually contain a "level" key as well so I can give them some padding based on the "level" while still showing up below their parent.
EDIT:
The JSON didn't seem to be valid, so I modified it a bit.
The main problem seems to be that you are not doing anything with the returned results of your recursive function. Unless using static inside a method does some magic that I don't know of...
So this section:
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
// What happens with the returned value?
$this->_flattenObject($member);
}
Should probably be more like this:
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
// Add the returned array to the array you already have
$flattened += $this->_flattenObject($member);
}
Here is code that works. It adds a field "level" to your objects, to represent how many levels deep in the original hierarchy they were.
<?php
$obj = json_decode('[{
"id": 1,
"name": "parent",
"children": [{
"id": 4,
"name": "child1",
"parent": 1
}, {
"id": 5,
"name": "child2",
"parent": 1
}]
}, {
"id": 2,
"name": "parent2"
}]');
function _flattenRecursive($array, &$flattened, &$level)
{
foreach ($array as $key => $member) {
$insert = $member;
$children = null;
if (is_array($insert->children)) {
$children = $insert->children;
$insert->children = array();
}
$insert->level = $level;
$flattened[] = $insert;
if ($children !== null) {
$level++;
_flattenRecursive($children, $flattened, $level);
$level--;
}
}
}
function flattenObject($array)
{
$flattened = [];
$level = 0;
_flattenRecursive($array, $flattened, $level);
return $flattened;
}
$flat = flattenObject($obj);
var_dump($flat);
?>
I need to iterate over objects in PHP and to apply a certain function on each and every single value in this object.
The objects are absolutely arbitrary. They can include vars, another objects, arrays, arrays of objects and so on...
Is there a generic method to do so? If yes, how?
Usage example:
RESTful API which receives requests in JSON format.
json_decode() is executed on request body and creates an arbitrary object.
Now, it is good, for example, to execute mysqli_real_escape_string() on every value in this object before further validations.
OBJECT EXAMPLE:
{
"_id": "551a78c500eed4fa853870fc",
"index": 0,
"guid": "f35a0b22-05b3-4f07-a3b5-1a319a663200",
"isActive": false,
"balance": "$3,312.76",
"age": 33,
"name": "Wolf Oconnor",
"gender": "male",
"company": "CHORIZON",
"email": "wolfoconnor#chorizon.com",
"phone": "+1 (958) 479-2837",
"address": "696 Moore Street, Coaldale, Kansas, 9597",
"registered": "2015-01-20T03:39:28 -02:00",
"latitude": 15.764928,
"longitude": -125.084813,
"tags": [
"id",
"nulla",
"tempor",
"do",
"nulla",
"laboris",
"consequat"
],
"friends": [
{
"id": 0,
"name": "Casey Dominguez"
},
{
"id": 1,
"name": "Morton Rich"
},
{
"id": 2,
"name": "Marla Parsons"
}
],
"greeting": "Hello, Wolf Oconnor! You have 3 unread messages."
}
If you just need to walk over the data and won't need to re-encode it, json_decode()'s second parameter, $assoc will cause it to return an associative array. From there, array_walk_recursive() should work well for what you're after.
$data = json_decode($source_object);
$success = array_walk_recursive($data, "my_validate");
function my_validate($value, $key){
//Do validation.
}
function RecursiveStuff($value, $callable)
{
if (is_array($value) || is_object($value))
{
foreach (&$prop in $value) {
$prop = RecursiveStuff($prop);
}
}
else {
$value = call_user_func($callable, $value);
}
return $value;
}
And use it like:
$decodedObject = RecursiveStuff($decodedObject, function($value)
{
return escapesomething($value); // do something with value here
});
You can just pass function name like:
$decodedObject = RecursiveStuff($decodedObject, 'mysqli_real_escape_string');