Related
I'm new to cakePHP and I have a Problem with Relations.
My Model Looks like this:
<?
class Operator extends AppModel
{
var $name = 'Operator';
var $hasOne = array('Contact','Adress','Land');
}
?>
and my Table Looks like this:
CREATE TABLE `operators` (
`id` varchar(45) NOT NULL,
`name` int(11) NOT NULL,
`adress_id` int(11) NOT NULL,
`land_id` int(11) NOT NULL,
`contact_id` int(11) NOT NULL,
`operator_category_id` int(11) NOT NULL,
`legal_form` varchar(45) DEFAULT NULL,
`affidavit` varchar(45) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `operator_category_fk_idx` (`operator_category_id`),
KEY `contact_id_idx` (`contact_id`),
KEY `adress_id_idx` (`adress_id`),
KEY `land_id_idx` (`land_id`),
CONSTRAINT `adress_id` FOREIGN KEY (`adress_id`) REFERENCES `adresses` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `contact_id` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `land_id` FOREIGN KEY (`land_id`) REFERENCES `lands` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `operator_category_fk` FOREIGN KEY (`operator_category_id`) REFERENCES `operator_category` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
)
but cakePHP doesn't Show any data from e.g. contacts
what am I doing wrong?
hasOne relationship only serves if you have 1 to 1 association to be able to access the associated model within the model which has no foreign key.
belongsTo
In your case, you want to use belongsTo, belongsTo is used as soon as you have a foreign key in your model pointing to another model, your Operator model has foreign keys pointing to Contact, Address, Land which are resp. contact_id, address_id and land_id.
Since the contact_id field is in the Operator model, then the Operator model belongsTo the Contact model.
class Operator extends AppModel {
public $belongsTo = array('Contact') ;
}
When fetching an Operator entry, you will get something like:
Array(
[Operator] => Array
(
[id] => 42,
[name] => 'operator',
[contact_id] => 33
)
[Contact] => Array
(
[id] => 33,
[name] => 'contact'
)
)
hasOne and hasMany
Since your Operator belongsTo Contact, your Contact hasOne OR hasMany Operator. Let's illustrate the difference between the two:
Each of your Contact entry is associated to only one Operator, you could add a operator_id foreign key inside your Contact model but it would be redundant because you already have the association using contact_id in Operator. Thus, what you do is create a hasOne association inside the Contact model, so Contact hasOne Operator.
class Contact extends AppModel {
public $hasOne = array('Operator') ;
}
Again, when fetching a Contact entry you will get:
Array(
[Contact] => Array
(
[id] => 33,
[name] => 'contact'
)
[Operator] => Array
(
[id] => 42,
[name] => 'operator',
[contact_id] => 33
)
)
Note that this is the same as when fecthing an Operator with a belongsTo Contact. The difference between hasOne and belongsTo is mainly which model has a foreign key pointing to the other.
Eah of your Contact entry is associated to multiple (many) Operator, in this case your Contact hasMany Operator.
class Contact extends AppModel {
public $hasMany = array('Operator') ;
}
Again, the output of $this->Contact->find():
Array(
[Contact] => Array
(
[id] => 33,
[name] => 'contact'
)
[Operator] => Array
(
[0] => Array
(
[id] => 42,
[name] => 'operator 42',
[contact_id] => 33
)
[0] => Array
(
[id] => 47,
[name] => 'operator 47',
[contact_id] => 33
)
)
)
hasAndBelongsToMany
Now, assume one Operator can have multiple Contact, and one Contact can serve multiple Operator. In this case, you need an extra table (which should be called operators_contacts if you follow CakePHP naming convention), with two fields operator_id and contact_id:
class Contact extends AppModel {
public $hasAndBelongsToMany = array('Operator') ;
}
class Operator extends AppModel {
public $hasAndBelongsToMany = array('Contact') ;
}
The output of $this->Contact->find('all') will be similar to the one using the hasMany relationship, main difference would be that their is no operator_id or contact_id field in the model.
Full example
Let's assume I have the following models: Company, Employee, Address, and the following constraints:
A Company has various Employee but only one CEO which is part of its Employee
A Company has one Address, and there is at most one company at each Address
You have the following tables:
CREATE TABLE companies (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
ceo_id INTEGER REFERENCES employees (id)
) ;
CREATE TABLE employees (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
company_id INTEGER REFERENCES companies (id)
) ;
CREATE TABLE addresses (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
company_id INTEGER REFERENCES companies (id)
) ;
Note that in this case, you cannot the table with all the constraints because you have a loop between companies and employees, you need to add the constraints after.
And the following CakePHP models:
class Company extends AppModel {
/* A Company has many Employee. */
public $hasMany = array('Employee') ;
/* A Company has one CEO, but the key is in the Company model,
so it is a belongsTo relationship. */
public $belongsTo = array(
'CEO' => array(
'className' => 'Employee', // It's an Employee
'foreignKey' => 'ceo_id'
)
) ;
/* A Company has one address. */
public $hasOne = array('Address') ;
} ;
class Employee extends AppModel {
/* An Employee belongs to a Company. */
public $belongsTo = array('Company') ;
} ;
class Address extends AppModel {
/* An address belongs to a Company. */
public $belongsTo = array('Company') ;
} ;
Notice I have put a company_id foreign key in the Address model, so Address belongsTo Company and Company hasOne Address, I could have put a address_id inside the Company model and I would have had Company belongsTo Address and Address hasOne Company. I made an arbitrary choice here, in a real application you should think of which of the two above cases is the most meaningful.
Also note that the way I defined my model, there is nothing that prevent an Employee to be the CEO of a Company without being one of its own Employee.
You can find more information in the CakePHP 2.x Book.
you should you use $belongsTo instead of hasOne relationship for this purpose.
public $belongsTo = array(
'Contact' => array(
'className' => 'Contact',
'foreignKey' => 'contact_id', // check this
'conditions' => '',
'fields' => '',
'order' => '',
'counterCache' => true
)
);
Operator belongsTo xyz, not hasOne
hasOne and belongsTo associations are similar, but not the same. To illustrate the difference consider the following schema:
users
id (int)
name (string)
addresses
id (int)
user_id (int)
is_home (bool)
... other fields ...
With User and Address models. Let's assume that there is only one "home" address per user, but a user is able to create multiple address records.
belongsTo means the table has a foreign key field in it (addresses.user_id) has* associations mean the other table has the foreign key (there is no users.address_id, User does not belongTo Address).
In summary, these statements would describe the model associations:
User hasMany Addresses
User hasOne home Address
Address belongsTo User
I am joining two tables tbl_complain_type and tbl_section.The sql for that tables are
CREATE TABLE IF NOT EXISTS `tbl_complain_type` (
`id` int(9) NOT NULL,
`section_id` varchar(250) NOT NULL,
`complains` varchar(250) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=20 ;
-- --------------------------------------------------------
--
-- Table structure for table `tbl_section`
--
CREATE TABLE IF NOT EXISTS `tbl_section` (
`id` int(9) NOT NULL,
`section` varchar(250) CHARACTER SET latin1 NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `tbl_complain_type`
--
ALTER TABLE `tbl_complain_type`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `tbl_section`
--
ALTER TABLE `tbl_section`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `tbl_complain_type`
--
ALTER TABLE `tbl_complain_type`
MODIFY `id` int(9) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=20;
--
-- AUTO_INCREMENT for table `tbl_section`
--
ALTER TABLE `tbl_section`
MODIFY `id` int(9) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=8;
tbl_complain_type and tbl_section have relation like tbl_section.id=tbl_complain_type.section_id .
I have made relation in ComplainType model as
public function getSection()
{
return $this->hasMany(Section::className(), ['id' => 'section_id']);
}
and in section model as
public function getComplainType()
{
return $this->hasOne(ComplainType::className(), ['id' => 'section_id']);
}
and I have modified ComplainTypeSearch model as
public function search($params) {
$query = ComplainType::find() ;
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->setSort([
'attributes' => [
'id',
'complains' => [
'asc' => ['complains' => SORT_ASC, 'complains' => SORT_ASC],
'desc' => ['complains' => SORT_DESC, 'complains' => SORT_DESC],
'label' => 'complains',
'default' => SORT_ASC
],
'section' => [
'asc' => ['tbl_section.id' => SORT_ASC],
'desc' => ['tbl_section.id' => SORT_DESC],
'label' => 'Section'
]
]
]);
if (!($this->load($params) && $this->validate())) {
$query->joinWith(['section']);
return $dataProvider;
}
$this->addCondition($query, 'id');
$this->addCondition($query, 'complains', true);
$this->addCondition($query, 'section_id');
return $dataProvider;
}
So in my index function, to display section with section name instead of section_id
I had to write as
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'id',
['attribute' => 'section',
'label'=>'section',
'format' => 'raw',
'value'=>function ($data) {
return $data['section'][0]->section ;
}, 'filter' => true,
],
'complains',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
while
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'id',
'section',
,
'complains',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
doesnt print section in view file. Is there any better way than using
$data['section'][0]->section ; to display the section ? I think using $data['section'][0]->section is not the right way. I would like to hear some suggestions or better way to achieve this.
You are only showing the first section, if the records has multiple sections it will show only the first section. if it has no section then you should see an error. Probably you should do something like this
[
'attribute' => 'section',
'label'=>'section',
'format' => 'raw',
'value'=>function ($model) {
$sections = [];
if($model->section) {
foreach($model->section as $section) {
$sections[] = $section->section
}
}
return implode(', ', $sections);
},
'filter' => true,
],
The Problem In A Nutshell
I want to retrieve data from Model A that HABTM Model B via a find() operation in Model B's controller without relying on extensive recursion.
$this->ModelB->bindModel('hasMany' => array('ModelAsModelBs'));
$this->ModelB->find('all', array('fields' => array('ModelA.*'))); //cond'ts below
I'm aware that bindModel() is required to do this, but I can't seem to get access to the associated model fields (ie. not just the HABTM table's fields, but the actual associated model) without multiple recursion.
It occurs to me that I may also be fundamentally misunderstanding something about how model relationships are supposed to interact, or be designed, or retrieved, etc.—in short, I recognize that the reason I may not be succeeding is that this may not be something I should be doing, henh. If this is so, I'd be equally happy learning how to do this better, because I frequently deal with very elaborate model relationships (I mostly do web development for academia and on-line course material/remote research).
Concrete Example / Actual Circumstances
The database has these tables, with id's as you'd expect:
Users
Courses
Modules
UsersCourses
UsersModules
CoursesModules
The model query I am trying execute is happening within the Users controller, and looks like this:
class Users extends AppController {
public function doSomething() {
$hasOne = array( 'hasOne' => array('CoursesModules','UsersModules'));
$this->User->Course->Module->bindModel($hasOne);
$conditions = array('`CoursesModules`.`course_id`' => $courseIds, //this is a known constraint within the app
'NOT' => array('`UsersModules`.`module_id` = `CoursesModules`.`module_id`' ));
$options = array( 'fields' => array('Module', 'Course.*'), // Note the attempt to get Course model information
'conditions' => $conditions,
'recursive' => 0);
$modules = $this->User->Course->Module->find('all', $options);
$this->set(compact('modules'));
}
}
This query results in:
Error: SQLSTATE[42S02]: Base table or view not found: 1051 Unknown table 'Course'
But neither can I use bindModel() to connect Courses to Modules. This strikes me as strange since the association path is Users->Courses->Modules. I can bring recursion up a notch, but it causes all sorts of hell that requires a lot of unbind() and also pulls a completely absurd amount of data.
A second oddity is that if I remove the Course.* from the fields list, the above query executes but doesn't work as I'd expect; I think this is correctly asking for all Modules listed in CoursesModules that are not also in UsersModules. Such data does exist in my records, yet isn't retrieved by this.
I realize I can get course_ids from the CoursesModules and then just do another find to get the Course model data, but that's a) not very Cake-like and b) a pain because I'd really appreciate having access to $modules['Module']['Course'] in the rendered view file.
Can anyone point me in the right direction here? Or, haha, god forbid, help me just build this MySQL query (I am all thumbs with MySQL joins)? Truly appreciated, thanks!
UPDATE
#Kai: To set up the relationships I set up my tables and baked'em. Worth noting, perhaps, is that I have a fairly basic grasp of MySQL and generally do everything through PhpMyAdmin. As for generating the initial Cake files, I use cake bake all and then modified things as I went. The SQL for the tables and the $hasAndBelongsToMany arrays from the respective models are posted at the end.
As to why I chose hasOne... I also assumed hasMany; using this relationship consistly generated 'column not found' errors from the tables I was binding (didn't matter which column). Meanwhile, the obviously wrong choice of hasOne worked, to some extent.
And finally, I have had a lurking suspicion that this containable behavior business might be what I was after, but I don't really understand it. As briefly as I can, this is the context for these models and the sorts of queries I'm trying to execute:
I'm building a program for a university faculty that will basically let profs have some online coursework. But the coursework (ie. modules) might be shared between different classes (ie. courses), and students might be in any or all classes. An additional constraint is that a student may have a choice of which modules she'll do in a given course—the prof may offer five of which they'll have to complete any three. So, when a student logs in, I need to be able to retrieve the modules they haven't completed yet, in the context of the courses they're in.
There are a plethora of similar queries I've got to make that are more or less of this nature. As it stands, I can achieve all this (and since this is on a deadline, have done so) through various uses of loadModel(), executing a simpler $this->Model->find(), sorting the result through some foreach logic, rinse repeat. Aside from being irritating, I'm also worried it's not scalable because of undue processing on my part, and finally... I hate doing things wrong, haha. I know that CakePHP can handle the questions I want to ask of my data, I just don't know how to ask them (and/or set up the data so such questions can be asked).
CREATE TABLE IF NOT EXISTS `modules` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain_id` int(11) NOT NULL,
`subject_id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`passing_score` int(11) NOT NULL,
`max_attempts` int(11) NOT NULL,
`allow_retry` int(11) NOT NULL,
`running_score` tinyint(1) NOT NULL,
`score_privacy` int(11) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `domain_id` (`domain_id`),
KEY `subject_id` (`subject_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`institution_id` int(11) NOT NULL,
`password` varchar(255) NOT NULL,
`firstname` varchar(63) NOT NULL,
`lastname` varchar(63) NOT NULL,
`email` varchar(255) NOT NULL,
`studentno` varchar(31) NOT NULL,
`claimed` tinyint(1) NOT NULL,
`verified` tinyint(1) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `group_id` (`group_id`),
KEY `institution_id` (`institution_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `courses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`institution_id` int(11) NOT NULL,
`coursecode` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`semester` varchar(7) NOT NULL,
`educator_id` int(11) NOT NULL,
`year` year(4) NOT NULL,
`description` text NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`educator_id`), /* educator is an alias of user in some cases */
KEY `institution_id` (`institution_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `users_courses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`course_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`,`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `users_modules` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`module_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`,`module_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
CREATE TABLE IF NOT EXISTS `courses_modules` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`course_id` int(11) NOT NULL,
`module_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `course_id` (`course_id`,`module_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Model Associations
Note: this is not comprehensive—hasOne,hasMany,belongsTo,etc. have been omitted so as to save on space; if need be I can post the entire model.
// from User Model
public $hasAndBelongsToMany = array(
'Course' => array(
'className' => 'Course',
'joinTable' => 'users_courses',
'foreignKey' => 'user_id',
'associationForeignKey' => 'course_id',
'unique' => 'keepExisting',
),
'Module' => array(
'className' => 'Module',
'joinTable' => 'users_modules',
'foreignKey' => 'user_id',
'associationForeignKey' => 'module_id',
'unique' => 'keepExisting',
)
);
// from Course Model
public $hasAndBelongsToMany = array(
'Module' => array(
'className' => 'Module',
'joinTable' => 'courses_modules',
'foreignKey' => 'course_id',
'associationForeignKey' => 'module_id',
'unique' => 'keepExisting',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
),
'User' => array(
'className' => 'User',
'joinTable' => 'users_courses',
'foreignKey' => 'course_id',
'associationForeignKey' => 'user_id',
'unique' => 'keepExisting',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
)
);
// from Module Model
public $hasAndBelongsToMany = array(
'Excerpt' => array(
'className' => 'Excerpt',
'joinTable' => 'excerpts_modules',
'foreignKey' => 'module_id',
'associationForeignKey' => 'excerpt_id',
'unique' => 'keepExisting',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
),
'Course' => array(
'className' => 'Course',
'joinTable' => 'courses_modules',
'foreignKey' => 'module_id',
'associationForeignKey' => 'course_id',
'unique' => 'keepExisting',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
)
);
As I understand it, this is the problem; A user has a list of available modules through their courses. We want to find available modules that the user has not completed. In other words, we have to find a user's course's modules which are not in that user's modules.
Here's the best way I can think of doing that:
$modules = $this->User->Module->find('all', array(
'joins' => array(
array(
'table' => 'courses_modules',
'alias' => 'CoursesModule',
'type' => 'LEFT',
'conditions' => array('CoursesModule.module_id = Module.id'),
),
array(
'table' => 'courses_users',
'alias' => 'CoursesUser',
'type' => 'LEFT',
'conditions' => array('CoursesUser.course_id = CoursesModule.course_id'),
),
array(
'table' => 'modules_users',
'alias' => 'ModulesUser',
'type' => 'LEFT',
'conditions' => array(
'ModulesUser.module_id = Module.id',
'ModulesUser.user_id' => $userId,
),
),
),
'conditions' => array(
'CoursesUser.user_id' => $userId,
'ModulesUser.id' => null,
),
'recursive' => -1,
));
Which should build a query like this:
SELECT Module.* FROM modules AS Module
LEFT JOIN courses_modules AS CoursesModule
ON (CoursesModule.module_id = Module.id)
LEFT JOIN courses_users AS CoursesUser
ON (CoursesUser.course_id = CoursesModule.course_id)
LEFT JOIN modules_users AS ModulesUser
ON (ModulesUser.module_id = Module.id
AND ModulesUser.user_id = $userId)
WHERE CoursesUser.user_id = $userId
AND ModulesUser.id IS NULL
The first two joins along with the user_id condition will get all the available modules. Rather than getting the user, their courses and then that course's modules we work backwards (kind of); we get the modules and join the courses to which we join the users. Once we add the user_id condition the association will be filtered and only modules that can be joined to the user will be found. Since we are starting with the modules there won't be any duplicates, we don't know (or care) which course was used to make the link.
With those available modules we then join the modules a user has completed and limit the records to those that couldn't be joined.
The CakePHP documentation has more info on joining tables.
Edit :
If you want to set the course IDs manually, you can do this:
$modules = $this->User->Module->find('all', array(
'joins' => array(
array(
'table' => 'courses_modules',
'alias' => 'CoursesModule',
'type' => 'LEFT',
'conditions' => array('CoursesModule.module_id = Module.id'),
),
array(
'table' => 'modules_users',
'alias' => 'ModulesUser',
'type' => 'LEFT',
'conditions' => array(
'ModulesUser.module_id = Module.id',
'ModulesUser.user_id' => $userId,
),
),
),
'conditions' => array(
'CoursesModule.course_id' => $courseIds,
'ModulesUser.id' => null,
),
'recursive' => -1,
));
There's something wrong with the way you set up your relationships. Could you post to show what you've done to set up the relationships, plus what the SQL query is that's giving the error? I'm also confused about what exactly your query is supposed to do.
I can make a few observations already though:
Course does not seem to be defined in your query, which is why you're getting a sql error. Probably there is something wrong with your relationships regarding Course.
If you're trying to do a HABTM relationship between Users and Modules and Courses and Modules, why are you doing creating a hasOne relationship between Module and CoursesModules and Module and UsersModules?
The short version of what I'm wondering is, shouldn't that be a hasMany instead of a hasOne?
The long version is, occasionally, when you have a join table like that, you'll need to for some reason create a relationship with the join table itself rather than just using HABTM (where when you get the data, the data in the join table is not included). Generally this is because there is data besides the two foreign keys that must also be stored in the join table. But in that case, you should be using what is known as a has many through relationship. If it's not necessary to use a has many through, you should just be using HABTM with users and courses, and not be creating relationships with directly with the join tables at all. http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany-through-the-join-model
Conditions for related models need to be in the contain options. Keep in mind that the normal behavior of Cake is to get related model data in additional queries, rather than doing a join and getting it all in one query. http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
I am trying to implement a simple messaging system for my users.
I am not an habtm expert, so any assistance will be appreciated!
Here is what I got this far, please make any suggestions!! (cake1.3)
CREATE TABLE IF NOT EXISTS `app_messages` (
`id` int(8) unsigned NOT NULL AUTO_INCREMENT,
`from_user_id` int(8) unsigned DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
`body` text,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `app_messages_users` (
`id` int(8) unsigned NOT NULL,
`message_id` int(8) unsigned NOT NULL,
`to_user_id` int(8) unsigned NOT NULL,
`read` tinyint(1) DEFAULT '0',
`replied` tinyint(1) NOT NULL DEFAULT '0',
`trash` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
Message.php
var $hasAndBelongsToMany = array(
'User' =>
array(
'className' => 'User',
'joinTable' => 'messages_users',
'foreignKey' => 'message_id',
'associationForeignKey' => 'to_user_id',
'unique' => true
)
);
User.php
var $hasMany = array(
'Message' => array(
'className' => 'message',
'foreignKey' => 'from_user_id',
'dependent' => true
)
So now, my question is, Am I doing this correct?
How do I go around creating a SEND MESSAGE function, to insert correct values to both tables?
I am a total noob regarding HABTM relationships, but I am trying to learn. Have spent several hours reading about it on the web, but still, need to ask if I am going the right way.
Thanks for your time!!
-Tom
Given that your join table has additional fields (read, replied, trash), you should not use an HABTM relationship. If you do, you won't be able to access them from your application. So instead, you should configure two hasMany associations through a new model for the join table. It is well explained here in the CookBook.
So you'll end up having something like this:
// User.php
class User extends AppModel {
public $hasMany = array(
'MessageUser' /* you can call this 'SentMessage' or something that makes more sense */
);
}
// Message.php
class Message extends AppModel {
public $hasMany = array(
'MessageUser'
);
}
// MessageUser.php
class MessageUser extends AppModel {
public $belongsTo = array(
'User', 'Message'
);
}
What is the reason for CakePHP to do the query "SET CHARACTER SET utf8" 38 times in a row for a simple find("all") (recursive property default at 0) ?
I use MySQL as database and let's say I create a users table which looks like this:
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
username char(50) UNIQUE,
password char(40),
post_id INT,
created DATETIME,
modified DATETIME,
CONSTRAINT PRIMARY KEY (id),
CONSTRAINT FOREIGN KEY (post_id) REFERENCES posts (id)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=INNODB;
and a posts table which looks like this:
CREATE TABLE posts (
id INT NOT NULL AUTO_INCREMENT,
title VARCHAR(30) NOT NULL,
content TEXT NOT NULL,
CONSTRAINT PRIMARY KEY (id)
) ENGINE=INNODB;
In my users_controller.php, I write in an action:
$log = $this->User->getDataSource()->getLog(false, false);
$this->User->find("all",
array(
'conditions' => array('id' => 3),
'recursive' => 0
));
debug($log);
I haven't tested this with the data above, but showed the tables above because they are similar to my current database. I use 25 tables with engine=innodb.
The debug is an array of 41 queries, where 2 queries retrieves the user and the post. The other 39 queries are identical and looks as follows:
Array
(
[log] => Array
(
[0] => Array
(
[query] => SET CHARACTER SET utf8;
[error] =>
[affected] => 0
[numRows] =>
[took] => 0
)
[1] => Array
(
[query] => SET CHARACTER SET utf8;
[error] =>
[affected] => 0
[numRows] =>
[took] => 0
)
.
.
.
[38] => Array
(
[query] => SET CHARACTER SET utf8;
[error] =>
[affected] => 0
[numRows] =>
[took] => 0
)
Is this supposed to happen? In my config/database.php file, it doesn't matter if I set encoding to 'utf8' or not. I do want 'utf8', but I don't want 38 unnecessary queries.
var $default = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'mylogin',
'password' => 'mypassword',
'database' => 'mydatabase',
'encoding' => 'utf8'
);
this sort of thing is expected in debug, cake uses the information to work. when you turn off debug it should all go away
Problem solved. It was a mistake from my part. The constructor in AppModel had the line:
$this->query( "SET CHARACTER SET utf8;" );