In my Laravel 8 project I'm dispatching a Job which runs and collects a bunch of data from the database, the data could be any amount ranging from a few hundred rows of data to potentially thousands, so could be quite memory intensive.
Upon returning the results, they're processed and added to a database table, and I'm hoping to have some kind of progress indication as a percentage that can be reported back to the user whilst the chunking is in progress, I have two tables, a reports and a reports_data table.
I've switched by query over to Laravel's chunk method, and am splitting the data collection into smaller bits as this should improve performance, but for some reason, to use my data as a whole, as if it were a collection I'm pushing it into an empty array called $res, but I'm getting an error so my job failsError: Call to a member function groupBy() on array:
Error: Call to a member function groupBy() on array
I'm wondering what I'm missing...
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$filters = json_decode($this->report->discovery_filters);
$data = [];
// create
foreach ($filters as $findable) {
$resultData = [];
// query data
if (isset($findable->query)) {
$this->setDynamicChartOptions();
$res = [];
$chunkData = DB::table($findable->query->table)
->select($findable->query->columns)
->where($findable->query->filterBy)
->orderBy($findable->query->orderBy->field, $findable->query->orderBy->direction)
->chunk(100, function ($chunkedResults) use ($res) {
foreach ($chunkedResults as $chunk) {
// how to update some kind of progress?
array_push($res, $chunk);
var_dump(count($res));
}
});
// $res expected as a collection? Maybe I can use the `collect` method?
if (isset($findable->query->useGrouping) && $findable->query->useGrouping) {
$results = $res->groupBy(function ($item, $key) use ($findable) {
$date = Carbon::parse($item->{$findable->query->groupBy});
return $date->format($findable->query->groupByFormat);
});
$results = $results->map(function ($item, $key) {
return $item[0];
});
$resultData = $results->flatten();
}
}
$res = [
'componentID' => $findable->componentID ?? 0,
'type' => $findable->type ?? '',
'name' => $findable->name ?? '',
'labelsKey' => $findable->query->labelsKey ?? '',
'dataKey' => $findable->query->dataKey ?? '',
'data' => $resultData ?? [],
'structure' => $this->getStructure($findable, $resultData)
];
array_push($data, $res);
}
// create our report data entry
$this->createReportData($data);
}
UPDATE:
I've tried chunking and grouping, the job fails:
$res = [];
$chunkData = DB::table($findable->query->table)
->select($findable->query->columns)
->where($findable->query->filterBy)
->orderBy($findable->query->orderBy->field, $findable->query->orderBy->direction)
->chunk(100, function ($chunkedResults) use ($res) {
$res[] = $chunkedResults;
foreach($res as $chunk) {
$chunk->groupBy();
}
});
This also fails...
res = [];
$chunkData = DB::table($findable->query->table)
->select($findable->query->columns)
->where($findable->query->filterBy)
->orderBy($findable->query->orderBy->field, $findable->query->orderBy->direction)
->chunk(100, function ($chunkedResults) use ($res) {
$res[] = $chunkedResults;
});
foreach($res as $chunk) {
$chunk->groupBy();
}
And this too, still doesn't seem to work in that it doesn't give back any collection, which is what I need for the rest of my code to work:
$res = [];
$chunkData = DB::table($findable->query->table)
->select($findable->query->columns)
->where($findable->query->filterBy)
->orderBy($findable->query->orderBy->field, $findable->query->orderBy->direction)
->chunk(100, function ($chunkedResults) use ($res) {
foreach ($chunkedResults as $key => $chunk) {
array_push($res, $chunk);
}
});
$res = collect($res);
Because $res = []; is an array, not an instance of eloquent's Illuminate\Support\Collection. Therefore, you can not call $res->groupBy(), as you are trying within the first if-statement.
Remove the ->chunk() method and get your data-chunk by using slice instead for example within a loop that always takes a slice of the data.
Optionally, call collect($res) to turn the array back into a collection. However, when having a Collection already, there is no point in making it into an array first just to cast it back directly afterwords. So I would go with the slice approach.
You could also - withing your chunk callback - do the following:
->chunk(100, function ($chunkedResults) use ($res) {
$res[] = $chunkedResults;
});
And then:
foreach($res as $chunk) {
$chunk->groupBy();
}
Related
Hello There I need Help To Update on Vehicle Wies on Specific Dates.I'm Attaching Form image and Here is my Controller code.
public function add_vehicle_expense(Request $request)
{
$veh = array('veh_reg_num' => $request->veh_reg_num);
foreach ($veh as $data) {
print_r($request->date[$data]);
dd();
$veh = Sale_report::where('date',$request->date[$data])->where('veh_reg_num', $request->veh_reg_num[$data])->update(['diesel_qty'=> $request->qty[$data], 'diesel_rate'=> $request->rate[$data], 'diesel_amount'=> $request->total_amount[$data], 'other_expense'=> $request->other_exp[$data]]);
}
if ($veh) {
return redirect()->back()->with('Data Store Successfully');
}
//$veh = Sale_report::where('date',$request->date)->where('veh_reg_num', $request->veh_reg_num)->update(['diesel_qty'=> $request->qty, 'diesel_rate'=> $request->rate, 'diesel_amount'=> $request->total_amount, 'other_expense'=> $request->other_exp]);
/* foreach ($request->date as $reg_num => $key ) {
$s = Sale_report::where('date',$request->date[$reg_num])->where('veh_reg_num',$request->veh_reg_num[$reg_num])->first();
$s->diesel_qty = $request->qty[$reg_num];
$s->diesel_rate = $request->rate[$reg_num];
$s->diesel_amount = $request->total_amount[$reg_num];
$s->other_expense = $request->other_exp[$reg_num];
$s->update();
} */
}
I have try to Update Data by matching Each Vehicle Number with Date Some times it Show ErrorException Attempt to read property on int,ErrorException Undefined array key "data"
I have Found The Solution of this Problem.
I've use implode(',' , $request->veh_reg_num) Then I use $datas = explode() function then I use foreach() With exploded Vairable as foreach($datas as $data => $value){
match the each row that with $data array then I Update the values to data base }
I have an array of 'items' which has properties like 'supplier' and 'price'. It looks this(I expanded the last object so you can see the entire structure):
I have the results as I want them, but I feel that I am using an inefficient method.
private function generateSupplierDate(Collection $collection, array $suppliers = []): Collection
{
$supplierGroups = $collection->pluck('items')->flatten()->whereIn('supplier', $suppliers)->groupBy('supplier')->values();
$results = [];
foreach ($supplierGroups as $suppliers) {
$cost = 0;
$name = '';
foreach ($suppliers as $supplier) {
$name = $supplier->supplier;
$cost += $supplier->quantity;
}
array_push($results, ['name' => $name, 'cost' => $cost]);
}
return $results;
}
My goal is to get this but more efficiently:
[
{
supplier: 'Walmart',
totalCost: 1000
},
{
supplier: 'Bestbuy'
totalCost: 100
}
]
I tried to use reduce but that didn't seem to give me the right solution. And honestly, I'm a Javascript developer and I could do this in the Frontend, but that seems inefficient.
Any help would be appreciated (very new to Laravel/PHP).
In my Laravel project I'm trying to flatten an array to ensure consistency, for some reason, the attached screenshot showing my data format returned from my project won't flatten with the flatten() method.
I get an error:
Error: Call to a member function flatten() on array
Which is quite generic, I've tried using ->toArray() before flattening but this doesn't give me any data, what am I doing wrong here?
The logic exists within a Laravel job, thus the console log
/**
* Group data
*
* #return void
*/
public function groupData(
$data,
$groupBy,
$groupByFormat,
$additionFromField = ''
) {
$results = $data->groupBy(function ($item, $key) use ($groupBy, $groupByFormat) {
$date = Carbon::parse($item->{$groupBy});
return $date->format($groupByFormat);
});
// grouping by some kind of total
if (!empty($additionFromField)) {
$results = $results->map(function ($item, $key) use ($additionFromField) {
$totals = 0;
foreach ($item as $key => $value) {
$totals += $value->{$additionFromField};
}
return [
'items' => count($item),
'total' => $totals ?? 0
];
});
$calcedData = [];
foreach ($results as $key => $result) {
array_push($calcedData, [
'period_to' => $key,
'items' => $result['items'],
'total' => $result['total']
]);
}
return $calcedData;
}
// standard grouping of data
$results = $results->map(function ($item, $key) {
return $item[0];
});
return $results;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$filters = json_decode($this->report->discovery_filters, true);
$data = [];
foreach ($filters as $findableKey => $findable) {
/*
** If there are datasets on the findable objec, then we assume
** that we can build up a chart or some data structure.
*/
if (isset($findable['datasets'])) {
$pushableDatasets = [];
foreach ($findable['datasets'] as $datasetKey => $dataset) {
// query data
if (isset($dataset['query'])) {
$additionFromField = $dataset['query']['additionFromField'] ?? '';
$res = DB::table($dataset['query']['table'])
->select($dataset['query']['columns'])
->where($dataset['query']['filterBy'])
->orderBy($dataset['query']['orderBy']['field'], $dataset['query']['orderBy']['direction'])
->get()
->chunk(100);
$res = $res->flatten();
if (isset($dataset['query']['useGrouping']) && $dataset['query']['useGrouping'] == 'yes') {
$results = $this->groupData(
$res,
$dataset['query']['groupBy'],
$dataset['query']['groupByFormat'],
$additionFromField
);
var_dump($results); // shown in the screenshot
$resultData = $results->flatten();
array_push($pushableDatasets, $this->getStructure($findable, $datasetKey, $resultData));
}
}
}
$findable['datasets'] = $pushableDatasets;
}
array_push($data, $findable);
}
}
Error: Call to a member function flatten() on array
The error message is quite accurate and descriptive. flatten is a member function of an object (in this case, the Laravel Collection object) and an array is not an object.
You need to convert the array to a collection first, then you can flatten it:
$flattened = collect($results)->flatten();
$new = collect($results)->flatten();
In your case you can also use array_flatten() I guess. Here is the documentation https://laravel.com/docs/5.1/helpers#method-array-flatten
I have a function getUnfilledOrders where I get Orders from the database and then use chunk to have them go to checkStatus 10 at a time. If I have 100 orders, the flow I believe would happen is checkStatus get will get called 10 times (since there are 100 orders).
Now once that completes, I want to have access to $totalOrders in getUnfulfilledOrders. Is this possible?
protected function getUnfulfilledOrders()
{
Order::where('order_status', '!=', true)
->where('tracking_number', '!=', null)
->limit(3000)
->chunk(10, function ($unfulfilledOrders) {
$this->checkStatus($unfulfilledOrders);
});
// how to do something now with $totalOrders once ALL Orders are processed 10 at a time;
}
protected function checkStatus($unfilledOrders)
{
$totalOrders = array();
foreach ($unfulfilledOrders as $unfulfilledOrder) {
// logic here
array_push($totalOrders, $unfulFilledOrder->id);
}
}
Like this:
protected function getUnfulfilledOrders()
{
$totalOrders = [];
Order::where('order_status', '!=', true)
->where('tracking_number', '!=', null)
->limit(3000)
// Add use (&$totalOrders)
->chunk(10, function ($unfulfilledOrders) use (&$totalOrders) {
$totalOrders = array_merge($totalOrders, $this->checkStatus($unfulfilledOrders));
});
// how to do something now with $totalOrders once ALL Orders are processed 10 at a time;
}
protected function checkStatus($unfilledOrders)
{
$totalOrders = array();
foreach ($unfulfilledOrders as $unfulfilledOrder) {
// logic here
array_push($totalOrders, $unfilledOrder->id);
}
// Return the generated array
return $totalOrders;
}
Here I initiated an empty array at the start of getUnfulfilledOrders() and merged anything returned by checkStatus() into it.
More on use ($var)
More on passing by reference (&$var)
I've tried to query using eloquent and fractal
$lists = Category::all();
$result = Fractal::collection($lists, new CategoryTransformer())->getArray();
and return it
return response()->json((['code' => "200", 'results' => $result]));
the json result is this:
{"code":"200","results":{"data":[{"id":"1","name":"Cafe","logo":null,"cover":""},{"id":"2","name":"SPA","logo":null,"cover":""},{"id":"3","name":"Hotel","logo":null,"cover":""}]}}
How to remove "data" after result?. So i can just get the array without "data".
I've tried:
$result = Fractal::collection($lists, new CategoryTransformer(), 'results')->getArray();
return (['code' => "200", $result]);
it return me :
{"code":"200","0":{"results":[{"id":"1","name":"Cafe","logo":"","cover":""},{"id":"2","name":"SPA","logo":"","cover":""},{"id":"3","name":"Hotel","logo":"","cover":""}]}}
There is leading '0' before results. how can i remove it?
Thanks
Try this:
return (['code' => "200", "results" => $result['results']);
I think the array method can't deal with a given array.
An other solution would be to add your results:
$result['code'] = 200;
return $result;
The data is just the key, I think it won't make any issues. If you still need to remove it, update getArray() function.
Put these Collection Macros in your AppServiceProvider::boot() method:
/**
* Remove the unnecessary nested 'data' keys
*
* #param string $case For consistency, define the type of keys that should be returned
*/
Collection::macro('fractal', function ($case = 'snake_case') {
//Handle this as a nested function to block access to the $depth flag.
//It's purpose is to indicate how deep the recursion is, and,
//more importantly, when it's handling the top-level instance
$recursion = function ($case = 'snake_case', array $items = [], $depth = 0) use (&$recursion) {
//If the array has only one element in it, and it's keyed off 'data', remove the wrapper.
//However, if it has a sibling element, such as 'meta', leave it alone
if (array_key_exists('data', $items) && count($items) == 1) {
$items = $items['data'];
}
$items = (new static($items))->mapWithKeys_v2(function ($item, $key) use (
$case,
$recursion,
$depth
) {
$key = $case ? $case($key) : $key;
//If the nested item is itself an array, recursively perform the same transformation
return is_array($item) ?
[$key => $recursion($case, $item, ++$depth)] : [$key => $item];
})->toArray();
//Maintain the top-level 'data' wrapper.
//This can easily be removed later in the controller if that's not needed either
$items = (!$depth && !array_key_exists('data', $items)) ?
['data' => $items] : $items;
return $items;
};
//Return the results in the form of an instance of Collection
return new static($recursion($case, $this->items));
});
/**
* Maintain non-sequential numeric keys when performing
* \Illuminate\Support\Collection::mapWithKeys() functionality
*
* Source: https://github.com/laravel/framework/issues/15409#issuecomment-247083776
*/
collect()->macro('mapWithKeys_v2', function ($callback) {
$result = [];
foreach ($this->items as $key => $value) {
$assoc = $callback($value, $key);
foreach ($assoc as $mapKey => $mapValue) {
$result[$mapKey] = $mapValue;
}
}
return new static($result);
});
Then run your Fractal results through it:
$results = collect($fractalResults)->fractal('camel_case')->get('data', []);