MySQL Error with straightforward polymorphic association in CakePHP - cakephp

I have 4 models, Application , Accommodation, Invoice and InvoiceItem
Invoice, Application and Accommodation all hasMany InvoiceItem
InvoiceItem belongsTo Application, Accommodation and Invoice
The purpose of all this is so that the line items of an Invoice (which come from the InvoiceItem model) can be associated with either an Accommodation or an Application via the InvoiceItem.foreign_id depending on whether InvoiceItem.class is set as Accommodation or Application
From what I understand, this is known as a polymorphic association.
Unfortunately, when I do a $this->Invoice->find('all'); with recursive set to 3 (or set to -1 with the appropriate fields specified in containable) I get this MySQL error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column
'InvoiceItem.class' in 'where clause'
SQL Query: SELECT `Application`.`id`, `Application`.`name`,
`Application`.`created`, `Application`.`updated` FROM `applications`
AS `Application` WHERE `Application`.`id` = 3786 AND
`InvoiceItem`.`class` = 'Application'
I don't understand why Cake would try to add InvoiceItem.class as a condition without creating a join for the InvoiceItem model.
Here are my models (note: I've cut down the number of fields in each for readability -- the Application and Accommodation models share very few similar fields in reality):
Accommodation Model
CREATE TABLE `accommodations` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Accommodation extends AppModel {
var $name = 'Accommodation';
var $hasMany = array(
'InvoiceItem' => array(
'className' => 'InvoiceItem',
'foreignKey' => 'foreign_id',
'conditions' => array('InvoiceItem.class' => 'Accommodation'),
)
);
}
?>
Application Model
CREATE TABLE `applications` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Application extends AppModel {
var $name = 'Application';
var $hasMany = array(
'InvoiceItem' => array(
'className' => 'InvoiceItem',
'foreignKey' => 'foreign_id',
'conditions' => array('InvoiceItem.class' => 'Application'),
)
);
}
?>
InvoiceItem model
CREATE TABLE IF NOT EXISTS `invoice_items` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`class` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`foreign_id` int(10) unsigned NOT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
<?php
class InvoiceItem extends AppModel {
var $name = 'InvoiceItem';
var $belongsTo = array(
'Accommodation' => array(
'foreignKey' => 'foreign_id',
'conditions' => array('InvoiceItem.class' => 'Accommodation')
),
'Application' => array(
'foreignKey' => 'foreign_id',
'conditions' => array('InvoiceItem.class' => 'Application')
),
'Invoice' => array(
'className' => 'Invoice',
'foreignKey' => 'invoice_id',
),
);
}
?>
Invoice model
CREATE TABLE IF NOT EXISTS `invoices` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
<?php
class Invoice extends AppModel {
var $name = 'Invoice';
var $hasMany = array(
'InvoiceItem' => array(
'className' => 'InvoiceItem'
'foreignKey' => 'invoice_id'
)
);
}
?>
I'm using CakePHP 2.4.0

class InvoiceItem extends AppModel,and bind no belongsTo.
class InvoiceItem extends AppModel {
var $name = 'InvoiceItem';
var $belongsTo = null
}
you can assign a suitable belongsTo to InvoiceItem at Runtime depend on content
$this->InvoiceItem->bindModel(array( 'belongsTo' => array(...) ) )

Related

How to define association between two model in cakephp 2.8.0

I have two model user and project and user_id is stored in project table how to implement inner join to display user name for users tables and projects table all fields.
this is my model for defining association
// project model
class Project extends AppModel
{
public $actsAs = array('Containable');
public $hasOne= array('User');
}
//Query Is
$conetent= $this->Project->find('all',array('limit' => 3,'joins' => array(array('table' => 'users','alias' => 'User1','type' => 'INNER','conditions' => array('Project.user_id =User1.id')))));
Best to do using cakephp way.
Add following code to your project controller.
$conetent= $this->Project->find('all',array('limit' => 3,'contain' => array('User')));
What you're looking for is a belongsTo relationship. This way, when you do a find in the Projects controller, it will pull the appropriate record from the User table that the project belongs to. For example (using CakePHP 2.x)
Database Tables
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(32) DEFAULT NULL,
`lastname` varchar(32) DEFAULT NULL,
`email` varchar(96) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
`lastlogin` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `projects` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`name` varchar(32) DEFAULT NULL,
`description` varchar(32) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Users Controller
class UsersController extends AppController {
public $uses = array('User');
public function index() {
}
public function create() {
}
public function update() {
}
public function delete() {
}
}
Users Model
<?php
class User extends AppModel {
}
Projects Controller
<?php
class ProjectsController extends AppController {
public $uses = array('Project');
public $components = array('Paginator', 'RequestHandler');
public function index() {
$projects = $this->Project->find('all'));
}
}
Projects Model
<?php
class Project extends AppModel {
public $useTable = 'projects';
public $belongsTo = array('User');
}
The find in the projects controller should yield a result of something like this:
array(
(int) 0 => array(
'Project' => array(
'id' => '1',
'firstname' => 'John',
'lastname' => 'Smith',
'email' => 'john.smith#domain.tld',
'password' => '******',
'created' => '2016-10-06 00:00:00',
'modified' => '2016-10-06 00:00:00',
'lastlogin' => '2016-10-06 00:00:00'
),
'User' => array(
'id' => '1',
'user_id' => '1',
'name' => 'Project Name',
'description' => 'Project Description',
'created' => '2016-10-06 00:00:00',
'modified' => '2016-10-06 00:00:00'
)
)
)

CakePHP counterCache associated table field won't update on delete()

I have the following tables: causes, users, transactions
Cause hasMany Transaction and User hasMany Transaction
Transaction belongsTo Cause and Transaction belongsTo User
Both my causes and users tables have transaction_count fields and my belongsTo has counterCache => true on both associations.
Both of my hasMany clauses have dependent => true and my foreignKeys are set up correctly in that if I delete a Cause, all of it's associated Transactions are deleted as well.
It is also working, in that when I create a Transaction, the transaction_count field in both my users and causes tables updates correctly. Here is the breakdown:
1.) Create a cause
2.) Create a transaction
RESULT: causes[ 15 ][ 'transaction_count' ] increases by 1 correctly
RESULT: users[ 1 ][ 'transaction_count' ] increases by 1 correctly
3.) Manually delete a transaction ( $transaction->delete( $id ) )
RESULT: causes[ 15 ][ 'transaction_count' ] decreases by 1 correctly
RESULT: users[ 1 ][ 'transaction_count' ] decreases by 1 correctly
4.) FAILURE:
If I create the Cause and Transaction and then delete the Cause using $this->Cause->delete( $id ), even though all of the transactions are deleted in the database (along with the Cause), the transaction_count in my users table is not updated to reflect the decrease.
I imagine this is because $this->Transaction->delete() is not getting called explicitly, but it is instead getting deleted due to its association with Cause?
Is there a workaround for this?
Tables
CREATE TABLE `causes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`transaction_count` int(10) unsigned NOT NULL DEFAULT '0'
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `transactions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cause_id` int(10) unsigned NOT NULL DEFAULT '0',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`name` varchar(255) NOT NULL
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`transaction_count` int(10) unsigned NOT NULL DEFAULT '0'
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
User.php
App::uses('AppModel', 'Model');
class User extends AppModel {
public $hasMany = array(
'Transaction' => array(
'dependent' => true
)
);
}
Cause.php
App::uses('AppModel', 'Model');
class Cause extends AppModel {
public $hasMany = array(
'Transaction' => array(
'dependent' => true
)
);
}
Transaction.php
App::uses('AppModel', 'Model');
class Transaction extends AppModel {
public $belongsTo = array(
'User' => array(
'counterCache' => true
),
'Cause' => array(
'counterCache' => true
)
);
}
Controller
$this->Cause->query('TRUNCATE TABLE users');
$user['User'] = array(
'name' => 'aaa'
);
$this->Cause->Transaction->User->create($user);
$this->Cause->Transaction->User->save(null, false);
$userId = $this->Cause->Transaction->User->getLastInsertID();
$this->Cause->query('TRUNCATE TABLE causes');
$cause['Cause'] = array(
'name' => 'aaa'
);
$this->Cause->create($cause);
$this->Cause->save(null, false);
$causeId = $this->Cause->getLastInsertID();
$this->Cause->query('TRUNCATE TABLE transactions');
$transaction['Transaction'] = array(
'name' => 'aaa',
'user_id' => $userId,
'cause_id' => $causeId
);
$this->Cause->Transaction->create($transaction);
$this->Cause->Transaction->save(null, false);
$this->Cause->Transaction->create($transaction);
$this->Cause->Transaction->save(null, false);
$transaction['Transaction'] = array(
'name' => 'aaa',
'user_id' => $userId,
'cause_id' => 99
);
$this->Cause->Transaction->create($transaction);
$this->Cause->Transaction->save(null, false);
$this->Cause->delete($causeId);
$cause = $this->Cause->find('first', array(
'conditions' => array(
'Cause.id' => $causeId
)
));
$transactions = $this->Cause->Transaction->find('all', array(
'conditions' => array(
'Transaction.cause_id' => $causeId
)
));
$user = $this->Cause->Transaction->User->find('first', array(
'conditions' => array(
'User.id' => $userId
)
));
pr($cause);
pr($transactions);
pr($user);
if (is_a($this->Cause, 'Cause')) {
echo 'Cause';
}
if (is_a($this->Cause->Transaction, 'Transaction')) {
echo 'Transaction';
}
if (is_a($this->Cause->Transaction->User, 'User')) {
echo 'User';
}
Result
Array
(
)
Array
(
)
Array
(
[User] => Array
(
[id] => 1
[name] => aaa
[transaction_count] => 1
)
)
CauseTransactionUser
It works ok.
Double check for typos in models and check if cake is using your models and not the ones that are created on the fly if some model is missing.

Column not found, retrieving data associated model

I've got the following find() function
$this->User->find('all',array(
'conditions' => array('User.id' => $this->Auth->user('id')),
'fields' => array('User.id','UserRole.id')
));
And the following associations are defined
// UserRole.php
class UserRole extends AppModel {
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
}
// User.php
class User extends AppModel {
public $hasMany = array(
'UserRole' => array(
'className' => 'UserRole',
'foreignKey' => 'user_id',
'dependent' => false,
)
);
}
And In App model recursive is set to -1
public $recursive = -1;
The database tables are the following:
CREATE TABLE IF NOT EXISTS `user_roles` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`event_id` int(10) unsigned NOT NULL,
`role_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`first_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`last_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
But I get the vollowing error
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'UserRole.id' in 'field list'
SQL Query: SELECT `User`.`id`, `UserRole`.`id` FROM `atlanta`.`users` AS `User` WHERE `User`.`id` = 5
I get that cake can't find the UserRole.id, but what did I do wrong?
Either you use some behavior like Containable and your find() will look like this
$this->User->find('all',array(
'conditions' => array('User.id' => $this->Auth->user('id')),
'contain' => array(
'UserRole' => array(
'fields'=>array('id')
)
),
'fields' => array('id')
));
OR you set recursive to 1
$this->User->find('all',array(
'conditions' => array('User.id' => $this->Auth->user('id')),
'fields' => array('User.id'), // UPDATE: unfortunately you cant specify associated models fields without behavior
'recursive' => 1
));
The thing is that when recursive is set to -1 or 0 it does not fetch associated models unless some behavior takes care of that for e.g. Containable or Linkable.

Polymorphic associations in CakePHP2

I have 3 models, Page , Course and Content
Page and Course contain meta data and Content contains HTML content.
Page and Course both hasMany Content
Content belongsTo Page and Course
To avoid having page_id and course_id fields in Content (because I want this to scale to more than just 2 models) I am looking at using Polymorphic Associations. I started by using the Polymorphic Behavior in the Bakery but it is generating waaay too many SQL queries for my liking and it's also throwing an "Illegal Offset" error which I don't know how to fix (it was written in 2008 and nobody seems to have referred to it recently so perhaps the error is due to it not having been designed for Cake 2?)
Anyway, I've found that I can almost do everything I need by hardcoding the associations in the models as such:
Page Model
CREATE TABLE `pages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Page extends AppModel {
var $name = 'Page';
var $hasMany = array(
'Content' => array(
'className' => 'Content',
'foreignKey' => 'foreign_id',
'conditions' => array('Content.class' => 'Page'),
)
);
}
?>
Course Model
CREATE TABLE `courses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Course extends AppModel {
var $name = 'Course';
var $hasMany = array(
'Content' => array(
'className' => 'Content',
'foreignKey' => 'foreign_id',
'conditions' => array('Content.class' => 'Course'),
)
);
}
?>
Content model
CREATE TABLE IF NOT EXISTS `contents` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`class` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`foreign_id` int(11) unsigned NOT NULL,
`title` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`content` text COLLATE utf8_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
<?php
class Content extends AppModel {
var $name = 'Content';
var $belongsTo = array(
'Page' => array(
'foreignKey' => 'foreign_id',
'conditions' => array('Content.class' => 'Page')
),
'Course' => array(
'foreignKey' => 'foreign_id',
'conditions' => array('Content.class' => 'Course')
)
);
}
?>
The good thing is that $this->Content->find('first') only generates a single SQL query instead of 3 (as was the case with the Polymorphic Behavior) but the problem is that the dataset returned includes both of the belongsTo models, whereas it should only really return the one that exists. Here's how the returned data looks:
array(
'Content' => array(
'id' => '1',
'class' => 'Course',
'foreign_id' => '1',
'title' => 'something about this course',
'content' => 'The content here',
'created' => null,
'modified' => null
),
'Page' => array(
'id' => null,
'title' => null,
'slug' => null,
'created' => null,
'updated' => null
),
'Course' => array(
'id' => '1',
'title' => 'Course name',
'slug' => 'name-of-the-course',
'created' => '2012-10-11 00:00:00',
'updated' => '2012-10-11 00:00:00'
)
)
I only want it to return one of either Page or Course depending on which one is specified in Content.class
UPDATE: Combining the Page and Course models would seem like the obvious solution to this problem but the schemas I have shown above are just shown for the purpose of this question. The actual schemas are actually very different in terms of their fields and the each have a different number of associations with other models too.
UPDATE 2
Here is the query that results from running $this->Content->find('first'); :
SELECT `Content`.`id`, `Content`.`class`, `Content`.`foreign_id`, `Content`.`title`,
`Content`.`slug`, `Content`.`content`, `Content`.`created`, `Content`.`modified`,
`Page`.`id`, `Page`.`title`, `Page`.`slug`, `Page`.`created`, `Page`.`updated`,
`Course`.`id`, `Course`.`title`, `Course`.`slug`, `Course`.`created`,
`Course`.`updated` FROM `cakedb`.`contents` AS `Content`
LEFT JOIN `cakedb`.`pages` AS `Page` ON
(`Content`.`foreign_id` = `Page`.`id` AND `Content`.`class` = 'Page')
LEFT JOIN `cakedb`.`courses` AS `Course` ON (`Content`.`foreign_id` = `Course`.`id`
AND `Content`.`class` = 'Course') WHERE 1 = 1 LIMIT 1
Your query is okay. Just filter empty values after find:
public function afterFind($results, $primary = false) {
return Set::filter($results, true);
}
Take a look at this question.
You can also try to provide conditions to you find query that Page.id in not null or Course.id is not null.

cakephp model association is not working

I have the table 'posts' and 'users':
CREATE TABLE `posts` (
`id` int(11) unsigned NOT NULL auto_increment,
`name` varchar(255) default NULL,
`date` datetime default NULL,
`content` text,
`user_id` int(11) default NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL auto_increment,
`name` varchar(100) default NULL,
`email` varchar(150) default NULL,
`firstname` varchar(60) default NULL,
`lastname` varchar(60) default NULL,
PRIMARY KEY (`id`)
);
and the model classes:
<?php
class Post extends AppModel {
var $name = 'Post';
var $belongsTo = array(
'User'=>array(
'className'=>'User',
'foreignKey'=>'user_id',
'conditions'=>null,
'fields'=>null)
);
}
?>
<?php
class User extends AppModel {
var $name = 'User';
var $hasMany = array('Post');
}
?>
I am testing using var $scaffold.
However, after I add some users, I can only see an empty select menu in the add post page, which means the association is not working. I don't know what is wrong with my code. Please help me out.
Thank you very much!
See the below url:-
http://book.cakephp.org/1.3/view/1042/belongsTo
http://book.cakephp.org/1.3/view/1043/hasMany
//Try This for belongsTo :--
<?php
class Profile extends AppModel {
var $name = 'Profile';
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
}
?>
Or
<?php
class Profile extends AppModel {
var $name = 'Profile';
var $belongsTo = array('User');
}
?>
//For hasmany
<?php
class User extends AppModel {
var $name = 'User';
var $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'user_id',
'conditions' => array('Comment.status' => '1'),
'order' => 'Comment.created DESC',
'limit' => '5',
'dependent'=> true
)
);
}
?>
I am starting with cakePHP too and banged my head for some time before I realized that the "conventions" require the Model filename to be CamelCased.
Try using:
Post.php
User.php
otherwise it seems the models are totally ignored.

Resources