CakePHP call to undefined method stdClass::read() error - php

I'm new to CakePHP and I'm still learning the basics, through working in a live project and taking help from the CakePHP documentations when necessary. Currently, I'm having the following problem : I've recently changed my database table name and structure, so I was forced to change my view, controller and model names. After changing names, whenever I run the index.ctp page, I get the following error:
Fatal error: Call to undefined method stdClass::read() in C:\wamp\www\sdb\app\controllers
\home_loan_distributions_details_controller.php on line 32
Previously, my view folder was named home_loan_distributions, now it's renamed to home_loan_distributions_details.
My previous controller name was home_loan_distributions_controller.php and current name is home_loan_distributions_details_controller.php. The codes:
class HomeLoanDistributionsDetailsController extends AppController {
var $name = 'HomeLoanDistributionsDetails';
function index() {
$user = $this->Session->read('User');
$user_role = $user['User']['user_role_id'];
$actions = $this->Session->read('actions');
$display_actions = array();
foreach ($actions as $action) {
array_push($display_actions, $action['pm_controller_actions']['name']);
}
$this->set('display_actions', $display_actions);
$this->set('user_role', $user_role);
$branch_id = 18;
$this->set('branch_id', $branch_id);
$conditions = array('branch_id' => $branch_id);
$this->set('HomeLoanDistributionsDetails', $this->paginate($conditions));
$this->HomeLoanDistributionDetail->Branch->recursive = 0;
$this->set('BranchDetailInformation', $this->HomeLoanDistributionDetail->Branch->read(array('Branch.id', 'Branch.name', 'RegionalOffice.name', 'DistrictOffice.name', 'SubDistrictOffice.name', 'ClusterOffice.name'), $branch_id));
}
My model was previously named home_loan_distribution.php and now it's named home_loan_distribution_detail.php. The codes:
class HomeLoanDistributionDetail extends AppModel {
var $name = 'HomeLoanDistributionDetail';
var $actsAs = array('Logable' => array(
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'list', // options are 'list' or 'full'
'description_ids' => TRUE // options are TRUE or FALSE
));
var $validate = array(
'entry_date' => array(
'rule' => 'date',
'message' => 'Enter a valid date',
'allowEmpty' => true
),
'branch_id' => array('numeric'),
'customer_id' => array('numeric'),
'loan_amount' => array('numeric'),
'service_charge' => array('numeric'),
'security' => array('numeric'),
'loan_taken_term' => array('numeric'),
'purpose_id' => array('numeric'),
'installment_amount' => array('numeric'),
'installment_service_charge' => array('numeric'),
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'Branch' => array(
'className' => 'Branch',
'foreignKey' => 'branch_id',
'conditions' => '',
'fields' => 'id,name',
'order' => ''
)
);
function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array()) {
$recursive = 0;
$group = $fields = array('branch_id', 'entry_date');
$order = array('entry_date DESC');
$limit = 4;
$this->paginateCount($conditions);
return $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'recursive', 'group'));
}
function paginateCount($conditions = null, $recursive = 0, $extra = array()) {
$recursive = 0;
$group = $fields = array('branch_id', 'entry_date');
$order = array('entry_date DESC');
$results = $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive', 'group'));
return count($results);
}
}
What my guess is: probably I messed up the naming conventions while renaming everything. The problem is definitely within this line in the controller:
$this->set('BranchDetailInformation',
$this->HomeLoanDistributionDetail->Branch->read(array('Branch.id', 'Branch.name',
'RegionalOffice.name', 'DistrictOffice.name', 'SubDistrictOffice.name', 'ClusterOffice.name'),
$branch_id));
Whenever I comment out this line, I stop getting the above mentioned error message and my view page loads (although that still have some data missing - because I need those Branch related data to be displayed in my view.)
I can't figure out what exactly my problem is, but at least I know where it is. I need someone to pinpoint it.
My CakePHP version is 1.2.5, PHP version - 5.2

There is no function read for model.If you want to find some model data then try with -
$this->set('BranchDetailInformation', $this->HomeLoanDistributionDetail->Branch->find('all', $consitions);
$conditions will be the array of all requirements you want to provide. See the docs for more info.

Apparently, the problem seemed to be related to my 'className' => 'Branch' element of the Branch array used in my model, since the stdClass::read() method is related to classes and not models. But I discovered that the problem was elsewhere. This error was part of that problem, but it itself is not the actual problem.
I figured out this morning that my Model name is HomeLoanDistributionDetail, but my table name is home_loan_distributions_details (because someone else has changed the table name). CakePHP convention requires corresponding table name to be plural and model class name to be singular and CamelCased.
Quoting from the CakePHP Cookbook:
Model class names are singular and CamelCased. Person, BigPerson, and ReallyBigPerson are all examples of conventional model names.
Table names corresponding to CakePHP models are plural and underscored. The underlying tables for the above mentioned models would be people, big_people, and really_big_people, respectively.
Considering the above convention, I just had to rename my model class name from HomeLoanDistributionDetail to HomeLoanDistributionsDetail, in order to match with the table name home_loan_distributions_details. Also, I had to change the model file name from home_loan_distribution_detail.php to home_loan_distributions_detail.php.
After doing that, I stopped getting the error and I was successful in retrieving data from table and viewing it.

Related

deep association between models

hi guys i am new to cakephp
now i'm facing a little big problem
here is the situation
i hava a shop that hasMany Catalogs which is related to many products each product has a category
i want to fetch them all just by getting the shop
i dont know how to do it
trying to use hasMAny gives me just the ids
instead is there any way to get shop inside it array of catalogs each catalog has Product's array which has one array of category
thank you
Ok, I'm on my computer now :).
In ShopModel:
class ShopModel extends AppModel {
public $hasMany = array(
'Catalog' => array(
// binding params here...
),
);
}
In CatalogModel:
class CatalogModel extends AppModel {
public $hasMany = array(
'Product' => array(
// binding params...
),
),
}
... and this goes on...
If you don't want to get excessive data in all actions, you should set in AppModel:
class AppModel extends Model {
public $recursive = -1;
}
In the controller action where you call the find function with associations:
$this->Shop->Behaviors->load('Containable');
$big_array = $this->Shop->find('all', array(
'conditions' => array(
//...
),
'contain' => array(
'Catalog' => array(
'Product' => array(
// etc, you get the point :)
),
),
),
));
It is also nice to declare the $belongsTo associations too, so you can access anything from anywhere, something like this:
$this->Catalog->Behaviors->load('Containable');
$big_array = $this->Catalog->find('all', array(
'conditions' => array(
//...
),
'contain' => array(
'Product' => array(
// ...
),
'Shop' => array(
// ...
),
),
));
EDIT
I see you have a Product->Category relation that i guess would be defined with $belongsTo. If you do a query like the one above, you will get a lot of duplicate queries (same category in many products). You can use $this->Category->find('list') but very often I find this inappropriate as it is returning only one field (I would be grateful if someone knows a way how can I get more fields with the list type). For this purpose, my workaround is making a custom function in the Category model like this:
class Category extends AppModel {
public function getSorted ($options = array()) {
$temp= $this->find('all', $options);
$output = array();
foreach ($temp[$this->alias] as $row) {
$output[$this->alias][$row['id']] = $row;
}
unset($temp);
return $output;
}
}
Then in the controller I would declare two arrays, the big one without category association and the category list one:
$this->loadModel('Category');
$this->set('categories', $this->Category->getSorted());
This way, I can get the needed category row by category id wherever i need it in the view.
Do not use CakePHP association they are not good handling complex relationships, you will later face problems with....Instead create all your join queries on the fly...I am giving you one example below:
Create one function inside Shop model and join catalog and product as shown below:
$options = array(
'conditions' => array('Product.id'=>9),
'joins' => array(
array(
'alias' => 'Catalog',
'table' => 'catalogs',
'type' => 'LEFT',
'conditions' => array(
'Catalog.product_id = Product.id',
),
),
array(
'alias' => 'Product',
'table' => 'products',
'type' => 'LEFT',
'conditions' => array(
'Shop.id = Product.shop_id',
),
)
),
'fields' => array('Product.*'),
'group' => array('Product.id')
);
$returnData = $this->find('all',$options);
This will make coding little easier and you can escape from associations!

how to make chained model auto read the conditions?

I have two models called Batch and User
Batch has the following
public $belongsTo = array(
'Customer' => array(
'className' => 'User',
'foreignKey' => 'customer_id',
'conditions' => array('Customer.group_id' => CUSTOMERS),
'fields' => '',
'order' => '',
),
);
When I do the following:
$customers = $this->Batch->Customer->find('list');
I fully expected to get back just the users whose group_id matches CUSTOMERS. It returns ALL the records in the users table.
However, I actually have to write
$customers = $this->Batch->Customer->find('list', array('conditions' => array('Customer.group_id' => CUSTOMERS)));
Is there a way so that the chained model User knows that it is called as Customer by Batch and therefore automatically reads the correct conditions in the associations found in Batch model?
I want to make my code more readable hence the motivation for this question.
I want to write simply
$customers = $this->Batch->Customer->find('list');
or something similarly straightforward.
Of course, I realized that if I do the following:
$batches = $this->Batch->find('all');
The condition stated in the associations will be used. But I don't want to find batches. I want to find just customers.
I am using CakePHP 2.4
I think you can't
but you can create custom find types in User model file
public $findMethods = array('customer' => true); //this enable a custom find method named 'customer'
protected function _findCustomer($state, $query, $results = array()) {
if ($state === 'before') {
$query['conditions'] = array('group_id' => CUSTOMERS);
}
return parent::_findList($state, $query, $results);
}
and in BatchesController
$this->Batch->Customer->find('customer');
There are several ways to do this.
1)
do nothing.
Continue to use code like
$customers = $this->Batch->Customer->find('list', array('conditions' => array('Customer.group_id' => CUSTOMERS)));
2)
create a custom find method as suggested by arilia.
3)
write a getCustomers method inside Batch model
where it looks something like this:
public function getCustomers($type, $query = array()) {
if (empty($query['conditions'])) {
$query['conditions'] = array();
}
$query['conditions'] = array_merge($query['conditions'], array('Customer.group_id' => CUSTOMERS));
return $this->Customer->find($type, $query);
}
then you can call
$customers = $this->Batch->getCustomers('list');
UPDATE:
I have written a Plugin that helps with this kind of behavior, utilizing the 3rd solution.
class Batch extends AppModel {
public $name = 'Batch';
public $actsAs = array('UtilityBehaviors.GetAssoc');
public $belongsTo = array(
'Customer' => array(
'className' => 'User',
'foreignKey' => 'customer_id',
'conditions' => array('Customer.group_id' => 7),
'fields' => '',
'order' => '',
),
);
}
You can fetch just the customer data when you are in BatchesController this way:
$customers = $this->Batch->getAssoc('Customer', 'list');
$customers = $this->Batch->getAssoc('Customer', 'all');
$customerCount = $this->Batch->getAssoc('Customer', 'count');
This behavior has tests at travis and you can read about the tests written at github.

Cake PHP paginate multiple tables of one model

community,
there is a problem with multiple tables which receive their entries of the same model "Post" on the same page. When clicking a paginator-number the app will change the page for both tables.
I tried it with dummy classes in the AppModel which should work but I get an error saying an internal error has occurred (error 500). I found out that there is a problem with the corresponding SQL-statement with a not found row.
In the AppModel:
class PostHood extends Post {
public $useTable = 'posts';
};
That code uses the table "cake_posts" of "class Post extends AppModel". In the controller I tried to get the PostHood like following:
$this->paginate['PostHood'] = array(
'conditions' => array('OR' => array(stuff)),
'limit' => 5
);
$this->set('postsHood', $this->paginate('PostHood'));
In the view there is a foreach-loop using the $postsHood as $post.
Maybe you have an idea, thanks in advance :)
EDIT 1:
I got some error notices after changing the code. May be you have an idea what to do.
Change of the AppModel:
class PostHood extends AppModel {
var $name = 'Post';
public $useTable = 'posts';
};
The Controller:
$this->loadModel('PostHood');
$this->paginate = array(
'conditions' => array('OR' =>
array(
array('AND' => array(
array('PostHood.ZIPCODE LIKE' => $userArea . '%'),
array('PostHood.ALTDATE >' => date("Y-m-d")),
array('PostHood.AGENT' => '0'),
array('PostHood.OWNER <>' => $this->UserAuth->getUserId()),
array('PostHood.PARENTID' => '0'),
array('PostHood.ACCEPTED' => '0')
)), [MORE AND ARRAYS]
$this->set('postsHood', $this->paginate('PostHood'));
The corresponding error in the view:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Post.DATE' in 'order clause'
SQL Query: SELECT PostHood.id, PostHood.B/S, PostHood.H, PostHood.CITY,
PostHood.MARKET, PostHood.DATE, PostHood.ALTDATE, PostHood.TIME, PostHood.INCOME, PostHood.ZIPCODE, PostHood.ALIAS, PostHood.VEHICLE, PostHood.DELIVERYAREA, PostHood.SPACE, PostHood.STREET, PostHood.HOUSENUMBER, PostHood.NAME, PostHood.CART, PostHood.TEL, PostHood.OWNER, PostHood.created, PostHood.modified, PostHood.AGENT, PostHood.EXTRADATA, PostHood.PARENTID, PostHood.BRATED, PostHood.SRATED, PostHood.REQUESTED, PostHood.ACCEPTED FROM usr_web126986_4.cake_posts AS PostHood WHERE 1 = 1 ORDER BY Post.DATE asc LIMIT 5
Obviously Cake tries to fetch data with "PostHood" but the table "posts" which I actually want to use is listening to "Post.field". How can I fix that? Thanks :)
You should try something like this:
$this->paginate = array(
'conditions' => array('OR' => array(stuff)),
'limit' => 5
);
$this->set('postsHood', $this->paginate('PostHood'));
I am just curious as to why you want to structure your model extending another model.
Got it working with the great plugin DataTables. That overrides the cake-pagination and uses JSON to display the results.
In order to implement it into cake you need
1.) a cake component for interpreting sql-statements: https://github.com/cnizzdotcom/cakephp-datatable
2.) the plugin: http://www.datatables.net/index
3.) implementation:
$this->paginate = array(
'fields' => array('Model.field1', 'Model.field2'),
'conditions' => array( stuff )
);
$response = $this->DataTable->getResponse();
$this->set('response', $response);
$this->set('_serialize','response');
$encode = json_encode($response);
$this->set('dataTablesData', $encode);
Then you can grab the data in the view.

Validating URL-Parameter by using Model-Rules CakePHP

i just write my first article so please tell me if i've done something wrong!
My Problem: I want to validate data given by url.
../Logs/requests?from=2011-10-18T16:15:00&to=2011-10-18T16:30:00&fmt=csv
I have just find out that there is an option to validate with the rules added to the Model.
public $validate = array(
'request_id' => array(
'alphaNumeric' => array(
'rule' => 'alphaNumeric',
'required' => true,
'message' => 'Alphabets and numbers only'
),
)
);
Using "ModelName->set($params)" in the Controller and after that the "ModelName->validates()"-function should deliver the answer if its valid or not. The only differenz between my solution and the solution at http://book.cakephp.org/2.0/en/models/data-validation/validating-data-from-the-controller.html
is that my controller using a couple of Models to collect the data for the response.
The problem is that the "validates()"-function just return "valid" even if i put in special-characters or other stuff what should be "not valid"-signed by the model-rules.
Help!
This is not an answer, but added to assist the OP;
I've created a test controller/model to test your situation. I deliberately did not extend the 'AppController' / 'AppModel' to remove any code in those from causing problems.
My test model (app/Model/Some.php)
class Some extends Model
{
public $name = 'Some';
public $useTable = 'false';
public $validate = array(
'request_id' => array(
'alphaNumeric' => array(
'rule' => 'alphaNumeric',
'required' => true,
'message' => 'Alphabets and numbers only'
),
)
);
}
My test controller (app/Controller/SomeController.php)
class SomeController extends Controller
{
public $uses = array('Some');
public function index()
{
$this->autoRender = false;
$params = array('Some' => array('request_id'=>'4*G/&2'));
$this->Some->set($params);
$result = $this->Some->validates();
debug($result);
$params = array('Some' => array('request_id'=>'AAAA'));
$this->Some->set($params);
$result = $this->Some->validates();
debug($result);
}
}
Outputs:
\app\Controller\SomeController.php (line 32)
false
\app\Controller\SomeController.php (line 37)
true
This test setup seems to work as planned, so you may try to test these in your application as well to narrow down the cause of your problem. Maybe some behavior is attached to your AppModel that contains a 'beforeValidate()' callback and disables the validation of the request_id field?

CakePHP changing virtual fields at runtime

I have a Product model for a multi site application.
Depending on the domain(site) I want to load different data.
For example instead of having a name and description fields in my database I have posh_name, cheap_name, posh_description, and cheap_description.
if I set something up like this:
class Product extends AppModel
{
var $virtualFields = array(
'name' => 'posh_name',
'description' => 'posh_description'
);
}
Then it always works, whether accessed directly from the model or via association.
But I need the virtual fields to be different depending on the domain. So first I creating my 2 sets:
var $poshVirtualFields = array(
'name' => 'posh_name',
'description' => 'posh_description'
);
var $cheapVirtualFields = array(
'name' => 'cheap_name',
'description' => 'cheap_description'
);
So these are my 2 sets, but how do I assign the correct one based on domain? I do have a global function called isCheap() that lets me know if I am on the lower end domain or not.
so I tried this:
var $virtualFields = isCheap() ? $this->cheapVirtualFields : $this->poshVirtualFields;
This gives me an error. Apparently you cannot assign variables in a Class definition like this.
So I put this in my Product model instead:
function beforeFind($queryData)
{
$this->virtualFields = isCheap() ? $this->cheapVirtualFields : $this->poshVirtualFields;
return $queryData;
}
This works ONLY when the data is accessed directly from the model, DOES NOT work when the data is accessed via model association.
There has got to be a way to get this to work right. How?
Well if I put it in the constructor instead of the beforeFind callback it seems to work:
class Product extends AppModel
{
var $poshVirtualFields = array(
'name' => 'posh_name',
'description' => 'posh_description'
);
var $cheapVirtualFields = array(
'name' => 'cheap_name',
'description' => 'cheap_description'
);
function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->virtualFields = isCheap() ? $this->cheapVirtualFields : $this->poshVirtualFields;
}
}
However, I am not sure if this is a CakePHP no no that can come back to bite me?
seems like the issue could be that the model association is a model that is built on the fly. eg AppModel
try and do pr(get_class($this->Relation)); in the code and see what the output is, it should be your models name and not AppModel.
also try and use:
var $poshVirtualFields = array(
'name' => 'Model.posh_name',
'description' => 'Model.posh_description'
);
var $cheapVirtualFields = array(
'name' => 'Model.cheap_name',
'description' => 'Model.cheap_description'
);

Categories