Cakephp 3 Dynamic limit parameter when using contain - sorting

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;

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
];

Why i can't create fake data with faker when i use the unique method

I try to create fake data for a todo app project so i use factory for to do that
i use on the for the category Model this :
$factory->define(Category::class, function (Faker $faker) {
return [
'name' => $faker->name,
'order' => $faker->unique()->randomDigitNotNull,
];
});
and when i use tinker everything works perfectly but when i to the task model
$factory->define(Task::class, function (Faker $faker) {
return [
'category_id' => $faker->numberBetween($min = 1, $max = 6),
'name' => $faker->name,
'description' => $faker->text($maxNbChars = 200),
'satus' => $faker->boolean,
'expired_at' => $faker->dateTime($max = 'now'),
'order' => $faker->unique(true)->numberBetween(1, 50),
];
});
i receive an error
"OverflowException with message 'Maximum retries of 10000 reached without finding a unique value'" and i don't know why its not working
This works
for ($i = 1; $i < 10; $i++) {
$faker->unique()->randomDigitNotNull;
}
When you increase 10 to any value(12, 15, 25); it is going to give an exception because of the implementation of randomDigitNotNull method.
public static function randomDigitNotNull()
{
return mt_rand(1, 9);
}
Since you are saying unique and the method will return you a value in that range [1,9]. if your loop iterates more than 9 times than at least one value will not be unique.

Additional data in Laravel Resource

I use Laravel resource from the controller:
$data = Project::limit(100)->get();
return response()->json(ProjectResource::collection($data));
I like to pass additional information to the ProjectResource. How it's possible? And how can i access the additional data?
I tried like this:
$data = Project::limit(100)->get();
return response()->json(ProjectResource::collection($data)->additional(['some_id => 1']);
But it's not working.
What's the right way?
I like to access the some_id in the resource like this.
public function toArray($request)
{
return [
'user_id' => $this->id,
'full_name' => $this->full_name,
'project_id' => $this->additional->some_id
];
}
In your controller don't wrap return Resource in response()->json.
Just return ProjectResource.
So like:
$data = Project::limit(100)->get();
return ProjectResource::collection($data)->additional(['some_id => 1']);
Sorry for misunderstanding the question.
I don't think there is an option to pass additional data like this. So you will have to loop over the collection and add this somehow.
One option is to add to resources in AnonymousCollection. For example:
$projectResource = ProjectResource::collection($data);
$projectResource->map(function($i) { $i->some_id = 1; });
return $projectResource;
and then in ProjectResource:
return [
'user_id' => $this->id,
'full_name' => $this->full_name,
'project_id' => $this->when( property_exists($this,'some_id'), function() { return $this->some_id; } ),
];
Or add some_id to project collection befour passing it to ResourceCollection.

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();

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

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.

Resources