Laravel5.5 adding columns to a collection from a second one - laravel

I have a sql request concerning admin :
$admin = Admin::findOrFail(Auth::guard('admin')->user()->id);
from this sql request I also manage to get the users of the admin ...
$users = $admin->users;
Each users must follow training sessions. and I must calculate it by using SQL requests for each users ... So i wrote this foreach statement
foreach ($users as $user) {
$todo = User::select() .....where('users.id' = $user->id)->get();
$done = User::select() .....where('users.id' = $user->id)->get();
$totalTimes = $todo->toBase()->sum('dureeformation');
$spendTimes = $done->toBase()->sum('dureeformation');
$remainingTimes = $totalTimes - $spendTimes;
$timeData[] = ['id' => $user->id, 'totalTime' => $totalTimes, 'spendTime' => $spendTimes, 'remainingTime' => $remainingTimes];
}
the totalTimes, spendTimes and remainingTimes are operations on Collections and i get expected results ...
$users->each(function ($record) use ($timeData) {
$record['totalTime'] = $timeData[$record['id']]['totalTime'];
$record['spendTime'] = $timeData[$record['id']]['spendTime'];
$record['remainingTime'] = $timeData[$record['id']]['remainingTime'];
//dd($record['totalTime']);
});
After this :
$timeData = collect($timeData);
$timeData= $timeData->keyBy('id');
$users = collect($users->toArray());
I have an issue here :
$users->each(function ($record) use ($timeData) {
$record['totalTime'] = $timeData[$record['id']]['totalTime'];
$record['spendTime'] = $timeData[$record['id']]['spendTime'];
$record['remainingTime'] = $timeData[$record['id']]['remainingTime'];
//var_dump($record);
});
$record in the function gives me what i expect ... the var_dump($records) added columns where it was expected but i can't take those results out of the function.
I tried to do this :
$variable = $users->each(function ($record) use ($timeData)...
dd($variable)
but unsuccessfully ...

Looks to me like your $timeData array is built differently than you access it. In your code you are doing
$timeData[] = [
'id' => $user->id,
'totalTime' => $totalTimes,
'spendTime' => $spendTimes,
'remainingTime' => $remainingTimes,
];
which will produce an array like this
[
0 => [
'id' => 17,
'totalTime' => 5,
'spendTime' => 4,
'remainingTime' => 3
],
1 => [
'id' => 17,
'totalTime' => 7,
'spendTime' => 5,
'remainingTime' => 2
]
]
but what you actually want is to have the id as index, right? Then you'd have to build the array this way:
$timeData[$user->id] = [
'totalTime' => $totalTimes,
'spendTime' => $spendTimes,
'remainingTime' => $remainingTimes,
];
or, as an alternative, perform a different lookup:
$users->each(function ($record) use ($timeData) {
$times = array_first($timeData, function ($value, $key) use ($record) {
return $value['id'] === $record['id'];
});
$record['totalTime'] = $times['totalTime'];
$record['spendTime'] = $times['spendTime'];
$record['remainingTime'] = $times['remainingTime'];
});
Both solutions have pros and cons, though. On the one hand, using the user identifier as the index will produce a huge array that is only filled partially. On the other hand, the second solution is a bit slower. As long as you are not going to deal with thousands of entries in the $timeData array, this won't be an issue, though.

Related

Attempt to read property post_id on int

I have a small response from db model, and i colud rebase it and response in route, but i see error.
My Controller:
class PostController extends Controller {
public function getLastRecord() {
$lastRec = Post::latest('created_at')->first();
$res = [];
$res = collect($lastRec)->map(function($record) {
return [
'id' => $record->post_id,
'rec_name' => $record->post_name,
'user' => $record->user_id,
];
})->toArray();
return $res;
}
}
I want that response will be array
['id': 1, 'rec_name': 'test_name', 'user': 1]
instead names from db
['post_id': 1, 'post_name': 'test_name', 'user_id': 1]
Error:
Attempt to read property "post_id" on int
When you collect() a single Record, then call ->map() (or other iterative methods), it loops over your Model's columns, not multiple Post records. You can solve this by wrapping $lastRec in an array, or using ->get() instead of ->first():
$lastRec = Post::latest('created_at')->first();
return collect([$lastRec])->map(function($record) {
return [
'id' => $record->post_id,
'rec_name' => $record->post_name,
'user' => $record->user_id,
];
})->toArray();
// OR
$lastRecs = Post::latest('created_at')->get();
return $lastRecs->map(function ($record) {
return [
'id' => $record->post_id,
'rec_name' => $record->post_name,
'user' => $record->user_id,
];
})->toArray();
Or, since this is a single record (using ->first() only ever returns 1 record), you don't need to collect() or map() at all:
$lastRec = Post::latest('created_at')->first();
return [
'id' => $lastRec->post_id,
'rec_name' => $lastRec->post_name,
'user' => $lastRec->user_id
];

Modify/add/rename eloquent collection

Let's say I've got eloquent collection returns from:
$b = Book::get(); (columns: id, book_name, pages)
$m = Magazine::get(); (columns, id, mag_name, type)
How do I then:
Combine them in the same collection.
Rename ->book_name/mag_name to k
Add type and if book use 'null' as value
add ->hash (str::random)
I want to keep the collection to be able to use the benifits that come with it. ie. not convert it to an array.
Laravel 7
You can do it with collection merge method
$b = Book::get(['id','book_name as k','pages']);
$m = Magazine::get(['id','mag_name as k','type']);
$bb = $b->map(function ($book){
$book['type'] = null;
$book['hash'] = Str::random();
return $book;
});
$mm = $m->map(function ($magzine){
$magzine['hash'] = Str::random();
return $magzine;
});
$merged = $bb->merge($mm);
return $merged;
Assume we have the following collections:
$b = Book::get(); //(columns: id, book_name, pages)
$m = Magazine::get(); //(columns, id, mag_name, type)
Second, update the fields:
$books = $b->map(function ($book) => {
return [
'id' => $book->id,
'k' => $book->book_name,
'type' => null,
'hash' => Str::random()
];
});
$magazines = $b->map(function ($magazine) => {
return [
'id' => $magazine->id,
'k' => $magazine->mag_name,
'type' => $magazine->type,
'hash' => Str::random()
];
});
Third, merge the collections:
$result = $books->merge(magazines);

Map array values to collection of items

How would one do the following elegantly with laravel collections ?
Map the values of the $baseMap as keys to the collection.
The baseMap :
$baseMap = [
'name' => 'new_name',
'year' => 'new_year',
];
The collection :
$items = collect([
[
'name' => 'name1',
'year' => '1000',
'not_in_basemap' => 'foo'
],
[
'name' => 'name2',
'year' => '2000',
'not_in_basemap' => 'foo'
],
//...
]);
The end result :
$result =[
[
'new_name' => 'name1',
'new_year' => '1000',
],
[
'new_name'=> 'name2',
'new_year' => '2000',
],
];
I know how to do it in plain php , just wondering what a nice collection version would be. Thanks!
I tried to find collection methods, or php functions, but without success. Some dirty code that works with different keys from both sides (items and basemap).
$result = $items->map(function($item) use ($baseMap) {
$array = [];
foreach($baseMap as $oldKey => $newKey){
if(isset($item[$oldKey])){
$array[$newKey] = $item[$oldKey];
}
}
return $array;
});
$result = $result->toArray();
Thanks to #vivek_23 and #IndianCoding for giving me idea's I ended up with the following :
I made a small edit to make sure the mapping and the items keys lined up.
so you don't have to worry of misalignment and all in laravel collection !
$baseMap = collect($baseMap)->sortKeys();
$result = $items->map(function ($item) use ($baseMap) {
return $baseMap->values()
->combine(
collect($item)->sortKeys()->intersectByKeys($baseMap)
)
->all();
});
Use intersectByKeys to filter your baseMap keys with $items values.
$result = $items->map(function($item,$key) use ($baseMap){
return array_combine(array_values($baseMap),collect($item)->intersectByKeys($baseMap)->all());
});
dd($result);
Update:
In a pure collection way,
$baseMapCollect = collect($baseMap);
$result = $items->map(function($item,$key) use ($baseMapCollect){
return $baseMapCollect->values()->combine(collect($item)->intersectByKeys($baseMapCollect->all())->values())->all();
});
dd($result);
Here are my two cents, using map. Don't know how dynamic your collection should be, but knowing the keys I would do the following:
$baseMap = [
'name' => 'new_name',
'year' => 'new_year',
];
$items = collect([
[
'name' => 'name1',
'year' => '1000',
'not_in_basemap' => 'foo'
],
[
'name' => 'name2',
'year' => '2000',
'not_in_basemap' => 'foo'
],
])->map(function($item, $key) use ($baseMap) {
return [
$baseMap['name'] => $item['name'],
$baseMap['year'] => $item['year']
];
});

Inserting and updating records from 3 tables in laravel

I am storing records for my product transfer app using 3 tables in single action. Transferhistories, Warehouse1StockSummaries and Warehouse2StockSummaries.
storing records to trasnferinghistories is ok, and also the increment method I declare to Warehouse2StockSummaries is also working fine except for Warehouse1StockSummaries.
here's my store function,
public function store(Request $request)
{
$input = $request->all();
$items = [];
for($i=0; $i<= count($input['product_id']); $i++) {
// if(empty($input['stock_in_qty'][$i]) || !is_numeric($input['stock_in_qty'][$i])) continue;
if(!isset($input['qty_in'][$i]) || !is_numeric($input['qty_in'][$i])) continue;
$acceptItem = [
'product_id' => $input['product_id'][$i],
'transfer_qty' => $input['qty_out'][$i],
'user_id' => $input['user_id'][$i]
];
array_push($items, Transferhistories::create($acceptItem));
// dd($input);
//update warehouse 1 summary
$warehouse1summary = Warehouse1StockSummaries::firstOrCreate(
['product_id' => $input['product_id'][$i]],
['qty_in' => $input['qty_in'][$i],
'qty_out' => $input['qty_out'][$i]
]);
if (!$warehouse1summary->wasRecentlyCreated) {
$warehouse1summary->increment('qty_out', $input['qty_out'][$i]);
}
//update warehouse 2 summary
$stock2Summary = Warehouse2StockSummaries::firstOrCreate(
['product_id' => $input['product_id'][$i]],
['qty_in' => $input['qty_out'][$i],'qty_out' => null]);
if (!$stock2Summary->wasRecentlyCreated) {
$stock2Summary->increment('qty_in', $input['qty_in'][$i]);
}
}
return redirect()->route('transferHistory.index');
}
updating warehouse 1 summary is not doing what it should be.
any suggestion master? thank you so much in advance!
According to laravel, firstOrCreate does not save the value, so after you do:
$warehouse1summary = Warehouse1StockSummaries::updateOrCreate(
['product_id' => $input['product_id'][$i]],
['qty_in' => $input['qty_in'][$i],
'qty_out' => $input['qty_out'][$i]
]);
Edit
The method firstOrNew will return the first or a instance of the Model.
So what you wanna do is this:
$warehouse1summary = Warehouse1StockSummaries::firstOrNew(
['product_id' => $input['product_id'][$i]],
['qty_in' => $input['qty_in'][$i],
'qty_out' => $input['qty_out'][$i]
]);
if(isset($warehouse1summary->created_at)){
$warehouse1summary->qty_out = $warehouse1summary->qty_out + $input['qty_out'][$i];
}
$warehouse1summary->save();

Cakephp 3 Dynamic limit parameter when using contain

CakePHP Version 3.5.5
My end goal is to provide the user the functionality to change the amount of results displayed via a select list on the index view. Also I need the initial page load to be sorted by area_name asc.
// WHAT I'VE DONE
I changed where I was stipulated the limit parameter which can be seen below.
// AREAS CONTROLLER
public $paginate = [
'sortWhitelist' => [
'Areas.area_name', 'Users.first_name', 'Users.last_name'
]
//'limit' => 1, // REMOVED FROM HERE
//'order' => [ // REMOVED FROM HERE
//'Areas.area_name' => 'asc'
//]
];
public function index()
{
$query = $this->Areas->find('all')
->contain([
'Users'
])
->where(['Areas.status' => 1]);
$limit = 1;
$this->paginate = [
'order' => ['Areas.area_name' => 'asc'], // ADDED HERE
'limit' => $limit // ADDED HERE
];
$this->set('areas', $this->paginate($query));
}
And I declare the pagination sort links like:
// AREAS INDEX VIEW
<?= $this->Paginator->sort('Areas.area_name', __('Area Name')) ?>
<?= $this->Paginator->sort('Users.first_name', __('First Name')) ?>
<?= $this->Paginator->sort('Users.last_name', __('Last Name')) ?>
// RESULT
The above code works on all index methods within the application that don't use contain but when I implemented this solution here everything worked except I cannot sort on the associated data - IE: Users first and last name?
=========================================================================
WHAT I'VE TRIED
// Attempt 1
I added an initialize method above the public $paginate class like:
public function initialize()
{
$limit = 1;
}
public $paginate = [
'sortWhitelist' => [
'Areas.area_name', 'Users.first_name', 'Users.last_name'
]
'limit' => $limit,
'order' => [
'Areas.area_name' => 'asc'
]
];
public function index()
{
$query = $this->Areas->find('all')
->contain([
'Users'
])
->where(['Areas.status' => 1]);
$this->set('areas', $this->paginate($query));
}
And the view I left the same.
// Result for Attempt 1
syntax error, unexpected ''limit'' (T_CONSTANT_ENCAPSED_STRING), expecting ']' on line 36 which is 'limit' => $limit,
=========================================================================
// Attempt 2
I tried to add the limit parameter and order array to the query like:
public function index()
{
$limit = 1;
$query = $this->Areas->find('all')
->contain([
'Users'
])
->where(['Areas.status' => 1])
->order(['Areas.area_name' => 'asc'])
->limit($limit);
$this->set('areas', $this->paginate($query));
}
// Result for Attempt 2
The result set was not ordered by the area_name and not limited to 1 result.
=========================================================================
// Attempt 3
I then changed the query and tried the following just to see if I could get a dynamic limit working:
$limit = 1;
$query = $this->Areas->find('all')
->contain('Users', function ($q) {
return $q
//->order('Areas.area_name' => 'asc'),
->limit($limit);
})
->where(['Areas.status' => 1]);
$this->set('areas', $this->paginate($query));
// Result for Attempt 3
The result set was not limited to 1 result.
=========================================================================
ADDITIONAL INFORMATION
// USERS TABLE
$this->hasOne('Areas', [
'foreignKey' => 'user_id'
]);
// AREAS TABLE
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
I searched through the following cookbook sections (Pagination, Query Builder, Retrieving Data & Result Sets and Associations - Linking Tables Together) but I can't find a way to get this working so any help would be much appreciated.
Many thanks. Z.
You are overwriting the $paginate property in your index() method, so your settings including the whitelist are being lost.
Set the keys directly instead:
$this->paginate['order'] = ['Areas.area_name' => 'asc'];
$this->paginate['limit'] = $limit;

Resources