I am currently involved in a CakePHP project and do not know how I can pass a modified query/array to a paginator.
Here is my controller:
public function index($fooElement = '')
{
$query = $this->Properties->find()->where(['fooElement' => $fooElement]);
//The fooFunction needs an array cause for an internal call of cakes HASH::NEST function
$data= $this->FooModel->_fooFunction($query->enableHydration(false)->toList();
//Error: Not a paginable object
$data = $this->paginate($data)
$this->set(compact('fooElement', 'data'));
$this->set('_serialize', ['data']);
if (empty($fooElement)) {
$this->render('otherView');
}
}
EDIT: Here is the fooFunction:
public function _fooFunction($data)
{
$out = [];
$cache = [];
$nested = Hash::nest($data, ['idPath' => '{n}.id', 'parentPath' => '{n}.parent_id']);
$out = $this->_setOrderAndLevel($nested);
return $out;
}
protected function _setOrderAndLevel($items, $level = 0, $number = 0)
{
$out = [];
$items = Hash::sort($items, '{n}.orderidx');
foreach ($items as $item) {
$item['level'] = $level;
if (!empty($item['children'])) {
$children = $item['children'];
unset($item['children']);
$out[] = $item;
$out = array_merge($out, $this->_setOrderAndLevel($children, $level + 1));
} else {
$out[] = $item;
}
}
return ($out);
}
The _fooFunction
takes the casted database query, makes some adjustments, adds two new properties and returns a nested Array. It maps id
with parent_id
in order to get children and a level description. The level description will be used for indentations in the view to display a hierarchical order.
IMPORTANT NOTICE: I am already beware of TreeBehavior in CakePHP but the problem is that our database has no left/right fields and I am not able to add them. Within this project I have to choose this way.
However $data
contains exactly what I want but I need to transform it into a compatible object for pagination.
EDIT: Thanks to ndm I could build a paginable object with the necessary constraints. The last problem I still have in front of me is to merge all children and possible sub-children. A parent can have nth children and also a children can sometimes have nth sub-children. Therefore I solved this with a recursive call of my _setOrderAndLevel function within the fooFunction.
This is the current structure:
array(
[0] = fooEntity(
id = 1,
orderidx = 1,
parentId = null,
level = 0,
children(
id = 2,
orderidx = 2,
parentId = 1,
level = 1
children(
id = 3,
orderidx = 3,
parentId = 2,
level = 2
........
But it should be this:
array(
[0] = fooEntity(
id = 1,
orderidx = 1,
parentId = null
level = 0
[1] = fooEntity(
id = 2,
orderidx = 2,
parentId = 1,
level = 1
[2] = fooEntity(
id = 3,
orderidx = 3,
parentId = 2,
level = 2
........
I tried to build a second result formatter but it does not work:
...
return $results
->nest('id', 'parent_id', 'children')
->map($decorate);
})
->formatResults(function (\Cake\Collection\CollectionInterface $results) {
return $results->map(function ($data) {
call_user_func_array('array_merge', $data);
});
});
Maybe a "combine->" call could be the solution but I am not sure. Any help is welcome
Generally if you need to format the results in some way, you should most likely use a result formatter, in order to be able to keep query object intact, and rom looking at the resulting format that your function produces, that is what you should use in this case, a result formatter.
If you need the ordering you could do that on SQL level already, and for nesting the results you could use the result collection's
nest()
method, ie you could ditch using theHash
class:Note that you must return an instance of
\Cake\Collection\CollectionInterface
from the result formatter. The docs say that returning an(y) iterator would be enough, but as soon as there are additional formatters appended that expect a collection, things would break.See also