Pass dynamic variable in matching or innerJoinWith in cakephp3 - cakephp-3.0

I am passing static value to my following query, then it works well. If I change it dynamic, then it doesn't.
//works well
$users = $this->Users->find('all')
->where(['Users.role' => $role])
->innerJoinWith('UserDetail',function($query){
return $query->where(['UserDetail.state' => "chandigarh"]);
})->toArray();
// don't work
$state="chandigarh";
$users = $this->Users->find('all')
->where(['Users.role' => $role])
->innerJoinWith('UserDetail',function($query){
return $query->where(['UserDetail.state' => "$state"]);
})->toArray();
How can I pass my $state variable dynamic to this?

This is how PHP works, it's not really a CakePHP related problem
That variable is not available inside the scope of the anonymous function $query
see example 3 here: http://php.net/manual/en/functions.anonymous.php
You should inherit the variable from the parent scope by using the USE contruct like this:
$state="chandigarh";
$users = $this->Users->find('all')
->where(['Users.role' => $role])
->innerJoinWith('UserDetail',function($query) use ($state) {
return $query->where(['UserDetail.state' => $state]);
})->toArray();

you should remove "" that cover "$state" that should be: $query->where(['UserDetail.state' => $state]) not $query->where(['UserDetail.state' => "$state"])

Related

Create dynamic request object in Laravel

I was looking for help creating an object of the type Illuminate\Http\Request. This article helped me understand the mechanism of the class, but I did not get the desired result.
Create a Laravel Request object on the fly
I'm editing the development code passed to me by the customer. The code has a function that gets a request parameter from vue and translates it to JSON:
$json = $request->json()->get('data');
$json['some_key'];
This code returned an empty array of data:
$json = $request->request->add([some data]);
or
$json = $request->request->replace([some data]);
this one returned an error for missing the add parameter
$json = $request->json->replace([some data]);
A variant was found by trying and errors. Maybe, it help someone save time:
public function index() {
$myRequest = new \Illuminate\Http\Request();
$myRequest->json()->replace(['data' => ['some_key' => $some_data]]);
$data = MyClass::getData($myRequest);
}
..
class MyClass extends ...
{
public static function getData(Request $request) {
$json = $request->json()->get('data');
$json['some_key'];
In addition, there are other fields in the class that you can also slip data into so that you can pass everything you want via Request
$myRequest->json()->replace(['data' => ['some_key' => $some_data]]);
..
$myRequest->request->replace(['data' => ['some_key' => $some_data]]);
..
$myRequest->attributes->replace(['data' => ['some_key' => $some_data]]);
..
$myRequest->query->replace(['data' => ['some_key' => $some_data]]);
$myRequest = new \Illuminate\Http\Request();
$myRequest->setMethod('POST');
$myRequest->request->add(['foo' => 'bar']);
dd($request->foo);
This from the link you shared works, give it a try. Thanks for sharing that link!

Yii Query Builder: Parametr binding using where() method

I have this code in my controller:
class ArchController extends Controller
{
public function actionIndex(string $date, array $rubric_id )
{
$articles = Article::find()->where('published < :date', [':date' => $date])
->andWhere(['in', 'rubric', $rubric_id])
->andWhere('ISNULL(arch)')->all();
...
It seems to me it is not safe because $rubric_id is user input. How can I make parametr binding, something like this:
':rubric_id' => $rubric_id
Yii uses parameter binding internally, so it is safe to use ->andWhere(['in', 'rubric', $rubric_id]). You can review implementation of InConditionBuilder to make sure of that.

Can indexby be used on associations directly in the query?

I have a number of situations where I need to cross-reference various records by ID, and find it's easiest to do so when the array is indexed by that ID. For example, Divisions hasMany Teams, Divisions hasMany Games, and Games belongTo HomeTeam and AwayTeam. When I want to read all of the teams and games in a division, I do something like this:
$division = $this->Divisions->get($id, [
'contain' => ['Teams', 'Games']
]);
I don't do
$division = $this->Divisions->get($id, [
'contain' => ['Teams', 'Games' => ['HomeTeam', 'AwayTeam']]
]);
because it seems that would increase memory requirements, especially when I'm further containing other models (People, etc.) in the Teams. So, instead I do
$division->teams = collection($division->teams)->indexBy('id')->toArray();
after the get to reindex that array, and then when I'm iterating through $division->games, to get the home team record I use $division->teams[$game->home_team_id]. This is all well and good (except that it sets the teams property as being dirty, a minor inconvenience).
But it seems that the queryBuilder functionality of the ORM is pretty magical, and I know that I can do
$teams = $this->Divisions->Teams->find()
->where(['division_id' => $id])
->indexBy('id')
->toArray();
to get an array of teams indexed how I want, so I'm wondering if there's some way to include indexBy on the associations. I tried
$division = $this->Divisions->get($id, [
'contain' => [
'Teams' => [
'queryBuilder' => function (Query $q) {
return $q->indexBy('id');
},
],
'Games',
]
]);
but, unsurprisingly, this didn't work. Any ideas?
Just for the record, guess you know this already, indexBy() doesn't belong to the query, but to the result set, so being able to call it requires the query to be executed first. It's not possible to use this for an association query builder, as it must return a query, not a result set.
While it would be possible to use result formatters for the associations and modify the result set accordingly, the problem is that the result set will hold all team results for all divisions, and when the team entities are being distributed on the various division entities that they belong to, the arrays will be "reindexed", respectively, the arrays will be populated without respect to the indices of the result set, so long story short, that won't work.
Global result formatter
However, a result formatter for the main query should work fine, and as you probably already figured, you can simply reset the dirty state afterwards in case it causes any problems, something like
$division = $this->Divisions
->find()
->contain([
'Teams'
])
->where([
'Divisions.id' => $id
])
->formatResults(function($results) {
/* #var $results \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface */
return $results
->map(function ($row) {
if (isset($row['teams'])) {
$row['teams'] = collection($row['teams'])->indexBy('id')->toArray();
}
if ($row instanceof EntityInterface) {
$row->dirty('teams', false);
}
return $row;
});
})
->firstOrFail();
Custom association and association specific result formatters
Another option would be to use a custom association class, which overrides ExternalAssociationTrait::_buildResultMap(), so that it respects the indices of the result set, as this is where the problem starts.
By default the associated entities are fetched from the result set and appended to a new array, which is later assigned to the respective association property on the entity the results belong to. So this is where the the keys from the possible custom indexed result set are being lost.
Here's an example, the change is really small, but I'm not sure about possible side effects!
src/Model/Association/IndexAwareHasMany.php
namespace App\Model\Association;
use Cake\ORM\Association\HasMany;
class IndexAwareHasMany extends HasMany
{
protected function _buildResultMap($fetchQuery, $options)
{
$resultMap = [];
$key = (array)$options['foreignKey'];
// grab the index here
foreach ($fetchQuery->all() as $index => $result) {
$values = [];
foreach ($key as $k) {
$values[] = $result[$k];
}
// and make use of it here
$resultMap[implode(';', $values)][$index] = $result;
}
return $resultMap;
}
}
Original: https://github.com/cakephp/...ORM/Association/ExternalAssociationTrait.php#L109
Now you must of course make use of the new association, to simplify it for this example, let's just override the table class' default hasMany() method
public function hasMany($associated, array $options = [])
{
$options += ['sourceTable' => $this];
$association = new \App\Model\Association\IndexAwareHasMany($associated, $options);
return $this->_associations->add($association->name(), $association);
}
And now, finally, you could use a result formatter for the association:
$division = $this->Divisions->get($id, [
'contain' => [
'Teams' => [
'queryBuilder' => function (Query $query) {
return $query
->formatResults(function($results) {
/* #var $results \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface */
return $results->indexBy('id');
});
}
],
'Games',
]
]);

How can i use a component inside a Cell?

I'm looking for a way to use component inside a cell, is there any way ?
I tried :
$this->loadComponent('SessionsActivity');
My Cell :
namespace App\View\Cell;
use Cake\Core\Configure;
use Cake\View\Cell;
class UserCell extends Cell
My query :
$user = $this->Users
->find()
->where([
'Users.id' => $this->request->id
])
->contain([
'Towns' => function ($q) {
return $q->find('short');
},
'Countries' => function ($q) {
return $q->find('short');
}
])
->map(function ($user) {
$user->online = $this->SessionsActivity->getOnlineStatus($user);
return $user;
})
->first();
Using the same example at the CakePHP cells documentation, suppose you want to show the inbox for the connected user. You'd want to access the Auth component to take the connected user id.
AFAIK, Cells act like view-controller, a view part display.ctp file, and a controller part where you access the model to generate data for the view.
I'm using CakePHP 3.0 and $this->_registry doesn't work.
You could try:
$this->SessionsActivity = $this->_registry->load('SessionsActivity');
it is another question if you should use a component here

getContent() not paging - only returns latest results

Within an extension (JSON access), I'm calling getContent() with:
$items = $this->app['storage']->getContent($contenttype, $options);
$response = $this->app->json($items);
return $response;
The options array is:
[
'limit' => 5,
'page' => 3
]
But getContent only returns the latest 5 results, not calculating the offset from the page variable. Is there another setting that I have to change to get it to recognise the paging?
Calling getContent you need to specify that you want paging turned on, here's some adding 'paging'=>true to the options should work correctly.