Controller Code:
public $paginate = [
'fields' => ['Subscribes.id'],
'limit' => 1,
'order' => [
'Subscribes.created' => 'desc'
]
];
Controller Method Code: Here $type is variable, value can be interger 1,2 3 etc
$customFinderOptions = [
'type' => $type
];
$this->paginate = [
'finder' => [
'typed' => $customFinderOptions
]
];
$contacts = $this->paginate($this->Subscribes);
Model Code:
public function findTyped(Query $query, array $options) {
$type = $options['type'];
return $query->where(['type' => $options['type']]);
}
Error: As you can see limit is set to 1 but it returns all the records. Which means pagination is not working with custom finder method.
It doesn't work, because you are overwriting your $paginate variable. Set it once in your controller action, use array_merge(), or update just single key:
$this->paginate["finder"] = [...];
Related
CakePHP Version: 4.0.1
Introduction
This problem follows on from another problem I had here where a not ideal solution was to redirect back to itself. Unfortunately the controller I was testing didn't have any associated columns so this new problem was not identified.
I referenced this in the cookbook.
Hopefully the below code will allow the problem to be reproduced.
Contacts Table
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('contacts');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Accounts', [
'foreignKey' => 'account_id'
]);
}
// Custom Finder
public function findSuperuserAllContacts(Query $query, array $options): object
{
$query
->where(['Contacts.status' => $options['status']])
->andWhere([
'Users.client_id' => $options['client_id'],
'Users.status' => 1
]);
return $query;
}
Contacts Controller
public $paginate = [
'sortWhitelist' => [
'Contacts.first_name',
'Contacts.last_name',
'Accounts.account_name',
'Users.first_name',
'Users.last_name',
'Contacts.primary_tel',
'Contacts.mobile',
'Contacts.email'
]
];
public function index() {
$query = (object) $this->Contacts->find('superuserAllContacts', [
'contain' => ['Users', 'Accounts'],
'status' => 1,
'client_id' => 1001
]);
$page = '';
$sort = 'Accounts.account_name';
$direction = 'asc';
$config = $this->paginate = [
'page' => $page,
'sort' => $sort,
'direction' => $direction,
'limit' => 10
];
$contacts = $this->Paginator->paginate($query, $config);
$this->set(compact('contacts'));
}
What Happens
The page displays a type error in the framework.
C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\View\Helper\PaginatorHelper.php
strtolower() expects parameter 1 to be a string, bool given
public function sortDir(?string $model = null, array $options = []): string
{
$dir = null;
if (empty($options)) {
$options = $this->params($model);
}
if (isset($options['direction'])) {
debug($options['direction']); // THIS IS FALSE NOT asc or desc?
$dir = strtolower($options['direction']);
}
if ($dir === 'desc') {
return 'desc';
}
return 'asc';
}
Stacktrace from error.log
2020-06-01 10:33:20 Error: [TypeError] strtolower() expects parameter 1 to be string, bool given in C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\View\Helper\PaginatorHelper.php on line 264
Stack Trace:
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\View\Helper\PaginatorHelper.php:264
- C:\xampp\htdocs\crm\templates\Contacts\index.php:5
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\View\View.php:1164
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\View\View.php:1125
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\View\View.php:750
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Controller\Controller.php:691
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Controller\Controller.php:533
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Controller\ControllerFactory.php:79
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\BaseApplication.php:229
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:77
- C:\xampp\htdocs\crm\vendor\cakephp\authentication\src\Middleware\AuthenticationMiddleware.php:122
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:73
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\I18n\Middleware\LocaleSelectorMiddleware.php:70
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:73
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:77
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Middleware\CsrfProtectionMiddleware.php:132
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:73
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:58
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Routing\Middleware\RoutingMiddleware.php:162
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:73
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Routing\Middleware\AssetMiddleware.php:68
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:73
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Error\Middleware\ErrorHandlerMiddleware.php:118
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:73
- C:\xampp\htdocs\crm\vendor\cakephp\debug_kit\src\Middleware\DebugKitMiddleware.php:60
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:73
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Runner.php:58
- C:\xampp\htdocs\crm\vendor\cakephp\cakephp\src\Http\Server.php:90
- C:\xampp\htdocs\crm\webroot\index.php:40
Request URL: /contacts
Referer URL: https://localhost/crm/welcome
Question
Why is a type error displayed instead of loading the index template with the sort on the account_name?
Thanks Z.
EDIT
I've just tried a fresh install with
composer self-update && composer create-project --prefer-dist cakephp/app:4.* crm
and baked users, contacts and accounts.
The new version is 4.0.8 and I added the code I have above in the new project but unfortunatley got exactly the same type error.
If the sort is on the same table, ie: Contacts.last_name the last name has the sort on it but if I change it to the associated table Accounts.account_name the type error is displayed.
The sort field whitelist must be passed in the config when using the paginator directly.
$config = $this->paginate = [
'sortWhitelist' => [
'Contacts.first_name',
'Contacts.last_name',
'Accounts.account_name',
'Users.first_name',
'Users.last_name',
'Contacts.primary_tel',
'Contacts.mobile',
'Contacts.email'
],
'page' => $page,
'sort' => $sort,
'direction' => $direction,
'limit' => 10
];
$contacts = $this->Paginator->paginate($query, $config);
$this->set(compact('contacts'));
The sort is now displayed on the account_name.
This is how I create my array fields:
public function index($slug, Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$page = $this->getDoctrine()->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
$fields = (array) $page;
return $this->render('mypage.html.twig', ['page' => $page, 'fields' => $fields]);
}
The output is:
array:3 [▼
"\x00App\Entity\Pages\x00id" => 3
"\x00App\Entity\Pages\x00name" => "cat"
"\x00App\Entity\Pages\x00color" => ""
]
But I actually need this result:
array:3 [▼
"id" => 3
"name" => "cat"
"color" => ""
]
According to the suggestions I made this change:
public function index($slug, Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$page = $this->getDoctrine()->getManager()->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
$fields = get_object_vars($page);
return $this->render('mypage.html.twig', ['page' => $page, 'fields' => $fields]);
}
But this outputs me an empty array.
You have two options:
1. Use Query::HYDRATE_ARRAY instead of findOneBy()
$query = $this->getDoctrine()
->getRepository(Pages:class)
->createQueryBuilder('p')
->getQuery();
$result = $query->getResult(Query::HYDRATE_ARRAY);
(stolen from this answer)
2. Use a Serializer
Use the Serializer Component or JMSSerializerBundle to serialize your entity object.
Here I am converting $allProductData to array because there I have to apply foreach loop and some conditions after that I am assigning $allProductData to pagination but I am getting error "Unable to locate an object compatible with paginate." so how can I convert array to Cake\ORM\Query object to pass into pagination?
$this->PurchaseRequisitionProducts->hasMany('PurchaseOrderProducts', [
'bindingKey'=>'product_id',
"foreignKey"=>'product_id'
]);
$allProductData = $this->PurchaseRequisitionProducts->find('all',[
'contain' => ['PurchaseOrderProducts'=>function($c){
return $c->where(['PurchaseOrderProducts.id IS NOT NULL']);
},'PurchaseOrderProducts.PurchaseOrder'=>function($p){
return $p->where(['PurchaseOrder.id IS NOT NULL'])
->where(['PurchaseOrder.is_approve'=>"Y"])
->where(['PurchaseOrder.po_type'=>1])
->where(['PurchaseOrder.status'=>1]);
},'PurchaseOrderProducts.PurchaseOrder.CompanyMaster','PurchaseRequisition.Users.EmployeeDetails.Departments','ProductCategory','ProductSubcategory','Currency','PurchaseRequisition','ProductsMaster','Uom'] ,
'conditions'=>[$condn,$conditions],
//"sortWhitelist"=>["id",'EmployeeDetails.first_name','Departments.department',"pr_reference_no","pr_date",'purchase_requisition_id', "ProductsMaster.product_name","ref_price","qty","approved_qty"],
'order'=>["PurchaseRequisitionProducts.id"=>"desc"],
])->toArray();
$pr_product = $this->paginate($allData)->toArray();
if($pr_product){
foreach($pr_product as $key1=>$prProd){
if(empty($prProd['purchase_order_products']) || !isset($prProd['purchase_order_products']) || $prProd['purchase_order_products']==null || $prProd['purchase_order_products']=='' || empty($prProd['purchase_order_products'])){
unset($pr_product[$key1]);
}
if(isset($prProd['purchase_order_products'])){
$supplier=[];
$poarray=[];
foreach($prProd['purchase_order_products'] as $key2=>$poProd){
if($poProd['purchase_order']==null || $poProd['purchase_order']=='' || empty($poProd['purchase_order'])){
unset($prProd['purchase_order_products'][$key2]);
}
$supplier[]=$poProd['purchase_order']['supplier_id'];
//debug($supplier);
$companies= $this->CompanyMaster->find('list', [
'keyField' => 'id','valueField' => 'Company_name',
'conditions'=>['id IN'=>$supplier],
])->toArray();
$pr_product[$key1]['supplier']=$companies;
}
if(empty($prProd['supplier'])){
unset($pr_product[$key1]);
}
}
}
}
You can do it this way also, you have to remove toArray() in order to make pagination work.
$allProductData = $this->PurchaseRequisitionProducts->find('all')->contain(['PurchaseOrderProducts']);
$pr_product = $this->paginate($allProductData);
Also, define this public variable
public $paginate = [
'limit' => 25,
'order' => [
'TableName.columnName' => 'asc' //not mandatory
]
];
Instead of adding to array in find all query you should add it on the result of pagination like:
$allProductData = $this->PurchaseRequisitionProducts->find('all',[
'contain' => ['PurchaseOrderProducts']
]);// remove toArray() from here
$pr_product = $this->paginate($allProductData)->toArray(); // AddtoArray() here
OR you can try like this:
$paginate = [
'contain' => ['PurchaseOrderProducts'],
'limit' => 10
];
$this->set('data', $this->Paginator->paginate($this->PurchaseRequisitionProducts->find('all'), $paginate)->toArray());
Hi folks! I'm trying to transfer data as array from the controller to the model, and then paste the data into the query builder, but the data must be in the same order as specified in the columns.
What options do I have?
And do you think this is a bad practice?
Controller:
$responseNotes = Model::factory('Notes')-> createTicket([
'description' => htmlspecialchars($_POST['description']),
'contact_id' => $_POST['contact_id'],
'pref_contact' => $_POST['pref_contact'],
'dog_id' => $_POST['document_id'],
'type' => $_POST['type'],
'owner_id' => Auth::instance()->get_user()->id,
'cc' => $_POST['cc-emails'],
'title' => $_POST['title']
]);
Model:
public function createNote(array $data)
{
$columns = [
'type',
'owner_id',
'cc',
'title',
'description',
'contact_id',
'pref_contact',
'dog_id'
];
if (!array_diff($columns, array_keys($data))) {
// All needed values exists
$result = DB::insert($this->NOTES, $columns)-> values($data)-> execute($this->SCHEMA);
}
return ($result) ? $result : false ;
}
Thanks to this answer. Solved this by:
// Order $data array according to $column.values
$orderedData = [];
foreach ($columns as $key) {
$orderedData[$key] = $data[$key];
}
$result = DB::insert($this->TICKETS, $columns)
-> values($orderedData)
-> execute($this->SCHEMA);
Why you don't use ORM Model?
in controller:
$responseNotes = ORM::factory('Notes')-> values([
'description' => htmlspecialchars($_POST['description']),
'contact_id' => $_POST['contact_id'],
'pref_contact' => $_POST['pref_contact'],
'dog_id' => $_POST['document_id'],
'type' => $_POST['type'],
'owner_id' => Auth::instance()->get_user()->id,
'cc' => $_POST['cc-emails'],
'title' => $_POST['title']
])
try{
$responseNotes->save();
} catch (ORM_Validation_Exception $ex) {
print_r($ex->errors('models'));
}
And don't use htmlspecialchars($_POST['description'])
In model class modify function (doc):
public function filters()
{
return array(
'description' => array( array('htmlspecialchars') ),
);
}
It looks like You have associative array with structure db_column=>value right? Than You can simply insert like this:
DB::Insert('table_name',array_keys($data))->values(array_values($data))->execute();
Controller:
$data =
array(
'ContentI18n' =>
array(
0 =>
array(
'title' => 'first',
'author' => 'first',
'source' => 'sgfsdfrst',
'lang' => 'fa',
),
),
'Content' =>
array(
'publish' => 1,
'type' => 3,
'pages' => 8,
'volume' => 7,
'modified' => '2012-05-27 14:16:37',
'created' => '2012-05-27 14:16:37',
'lang' => 'fa',
),
);
$this->Content->create();
$this->Content->saveAll($data);
Model:
public $hasMany = array(
'ContentI18n' => array(
'className' => 'ContentI18n',
)
);
beforeSave function in behavior:
public function beforeSave(Model $model) {
// Define the new Translate model
App::uses($model->name . 'I18n', 'Model');
$varI18n = $model->name . 'I18n';
$modelI18n = new $varI18n;
foreach ($model->data[$model->name] as $key => $data) {
if (!in_array($key, $this->setting))
$modelData[$model->name][$key] = $data;
else
$modelData[$model->name . 'I18n'][0][$key] = $data;
}
$modelData[$model->name . 'I18n'][0]['lang'] = $model->locale;
$modelData[$model->name]['lang'] = $model->locale;
$model->data = $modelData;
//pr($model->data);
return TRUE;
}
every things seems be cause when is save it directly it's save with saveAll. but when i use same structure of data in behavior did not work without any error.
I found out that beforeSave callback will not trigger by saveAll. for executing some code before saveAll we must override saveAll function in our model.
public function saveAll($data, $options = array()) {
/*
your code you want execute before saving...
*/
parent::saveAll($data, $options);
}
for other saving methods such as saveMany,saveAssociated,... beforeSave trigger by them.