Aman Rawat Aman Rawat - 7 months ago 122
PHP Question

Set Date as keyField in list Cakephp 3

I have used list to get data from the model and it works well

This example is from Cakephp docs

$query = $articles->find('list', [
'keyField' => 'slug',
'valueField' => 'title'
]);
$data = $query->toArray();


I have a table name holidays


id[int] | date[date] | name[varchar(200)] | created[datetime]


So want a list which has date as the key and holiday as value
Somthing like this

[
'2016-01-01'=>'New Year',
'2016-01-26'=>'Republic Day',
]


So i created model by cake bake and used this code to achive it

$holidays = TableRegistry::get('Holidays');
$holidays = $holidays->find('list',[
'keyField' => 'date',
'valueField'=>'name',
])->toArray();


But its giving me error


Illegal offset type
InvalidArgumentException


When i change the keyField from date with name it works great.

Here is the logs


2016-04-29 04:26:41 Error: [InvalidArgumentException] Illegal offset
type Request URL: /sfworxerp/api/attendances/getMonthAttendanceData
Stack Trace:
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Collection\Iterator\MapReduce.php(160):
Cake\Collection\Iterator\MapReduce::emit()
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Collection\CollectionTrait.php(386):
Cake\Collection\Iterator\MapReduce->emit('New Year',
Object(Cake\I18n\FrozenDate))
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Collection\Iterator\MapReduce.php(177):
Cake\ORM\ResultSet->Cake\Collection{closure}(Object(App\Model\Entity\Holiday),
0, Object(Cake\Collection\Iterator\MapReduce))
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Collection\Iterator\MapReduce.php(132):
Cake\Collection\Iterator\MapReduce->_execute() [internal function]:
Cake\Collection\Iterator\MapReduce->getIterator()
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Collection\Collection.php(50):
IteratorIterator->__construct(Object(Cake\Collection\Iterator\MapReduce))
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Collection\CollectionTrait.php(405):
Cake\Collection\Collection->__construct(Object(Cake\Collection\Iterator\MapReduce))
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\ORM\Table.php(1062):
Cake\ORM\ResultSet->combine('date', 'name', NULL)
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Datasource\QueryTrait.php(490):
Cake\ORM\Table->Cake\ORM{closure}(Object(Cake\ORM\ResultSet))
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\ORM\Query.php(1141):
Cake\ORM\Query->_applyDecorators(Object(Cake\ORM\ResultSet))
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Datasource\QueryTrait.php(272):
Cake\ORM\Query->_decorateResults(Object(Cake\ORM\ResultSet))
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\ORM\Query.php(871):
Cake\ORM\Query->_all()
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Datasource\QueryTrait.php(288):
Cake\ORM\Query->all()
F:\public_html\sfworxerp\src\Controller\Api\AttendancesController.php(133):
Cake\ORM\Query->toArray() [internal function]:
App\Controller\Api\AttendancesController->getMonthAttendanceData()
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Controller\Controller.php(429):
call_user_func_array(Array, Array)
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Routing\Dispatcher.php(114):
Cake\Controller\Controller->invokeAction()
F:\public_html\sfworxerp\vendor\cakephp\cakephp\src\Routing\Dispatcher.php(87): Cake\Routing\Dispatcher->_invoke(Object(App\Controller\Api\AttendancesController))
F:\public_html\sfworxerp\webroot\index.php(37):
Cake\Routing\Dispatcher->dispatch(Object(Cake\Network\Request),
Object(Cake\Network\Response)) {main}


Any help is highly appreciated

Answer

The Problem:

The problem is, for 'date' fields, CakePHP 3 returns an object, not a string:

'date' => object(Cake\I18n\FrozenDate) {
    'time' => '1997-01-03T00:00:00+00:00',
    'timezone' => 'UTC',
    'fixedNowTime' => false
},

Trying to use that as the key doesn't work, and throws the error you're seeing.

How to handle:

Per this area in the CakePHP book, to accommodate this, you can:

use closures to access entity mutator methods in your list finds.

TLDR Do this:

$query = $articles->find('list', [
    'keyField' => function ($e) {
         return $e->date->format('Y-m-d');
     },
    'valueField' => 'title'
]);

Explanation for how it works:

Basically, it puts the value of each entity (in your case each article) into the variable $e, and allows you to use/modify it's data, and return the string you want to use as the key (or value) field. In the case above, it's getting the date object, and formatting it into a string before returning it to be used as the key.