DataMapper ORM for Codeigniter Relations - php

I have a table ...
CREATE TABLE IF NOT EXISTS `messages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`to` int(11) NOT NULL,
`from` int(11) NOT NULL,
`subject` varchar(50) NOT NULL,
`message` varchar(1000) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
To and From is the primary key id from Users Table.
How can I get the user details when I get each message with CodeIgniter DataMapper.

You are missing a couple key points to using the DataMapper for CodeIgniter. First off you need to do some pretty simple and important things. DataMapper (DM) uses normalized db naming to find relationships. What this means is if you query your db. Now it's a little harder to use DM for two columns and I think that you really don't need it.
First if you don't use DM you really only need two queries
SELECT u.*, m.* FROM messages AS m, users AS u WHERE m.from = u.id AND m.id = SOME_ID.
This query will get you all user details and message details for some message ID.
Now this is semi-simple case because I am assuming a message can only be from one user.
For the to field on the other hand I will assume you should use a relational table. To use DM for it you have to name the table something like users_messages but again why do you need to use DM when it really is overkill.
Now for the from field you have a many to many relation because a message can have many users that it was to and a user can have many messages that they sent.
So create a table like this
CREATE TABLE message_to (
user_id BIGINT UNSIGNED NOT NULL,
message_to_id BIGING UNSIGNED NOT NULL,
PRIMARY KEY (user_id, message_to_id),
);
If you want to do it right you will also use foreign keys but that depends on your DB
Now you can query really easily and get all the users a message was sent to.
SELECT u.*, m.* FROM users AS u, m AS messages JOIN messages_to AS m_t ON (u.id = m_t.user_id)
And querying the other way around is just as easy, getting all the messages a user has sent.
Remember just because a tool like DM exists doesn't mean it is the best tool for the job and actually using DM in this case incurs a pretty decent overhead when it is not necessary.
Doing this with DM would require the same things you just cannot name your tables/columns as you see fit and you need a class for every table creating it's relationship with other tables.
Meaning you have a lot of extra work to use DM, and you need to learn their syntax.

What you're looking for is a self-relationship.
If this is a 'has_one' relation, you can do that with in-table foreign keys. You do have to follow the naming convention for keys (to_id and from_id instead of to and from).
Currently (v1.8.0) you can only have one relation between any two models:
$has_one = array(
'to' => array(
'class' => 'messages',
'other_field' => 'messages'
),
'messages' => array(
'other_field' => 'to'
)
);
}
See http://datamapper.wanwizard.eu/pages/advancedrelations.html for more information.

You have to make your models [models/users.php] and
[models/messages.php] like this:
class User extends DataMapper {
var $has_many = array(
'sent_message' => array(
'class' => 'Message',
'other_field' => 'sender',
),
'received_message' => array(
'class' => 'Message',
'other_field' => 'receiver',
),
);
}
class Message extends Datamapper {
var $has_one = array(
'sender' => array(
'class' => 'User',
'other_field' => 'sent_message',
),
'receiver' => array(
'class' => 'User',
'other_field' => 'received_message'
),
);
}
I Only have proviede $has_one and $has_many and you have to include the rest of models.
you have to deifne your tables like this:
Table Name: users fields: id, email, ....
Table Name: messages [this is the important table in this case] field:
id, sender_id, receiver_id, subject, message, created, ....
Now have to fill your database with example messages and then you can test like this:
for example User X is logged in and is now an object. You get users last 10 Messages like this:
$messages = new Message();
$messages->where_related_user('id', $user->id);
$messages->limit(10);
$messages->get();
you can get the reciever and sender of each message like this:
$messages = new Message();
$messages->include_related('sender');
$messages->include_related('receiver');
$messages->get();
Now print the name of each sender and receiver:
foreach($messages as $message):
echo $message->sender->name;
echo $message->receiver->name;
endforeach;

Related

cake php 3.x, model join 3 table

Im trying to join 3 tables in cake php.
I'll shorten the table I have to make it simple.
table_users(
id int primary key,
username varchar(10),
password varchar(10),
)
table_details(
id int primary key,
user_id int, //fk of table_users.id
//more fields here
)
table_ot(
id int primary key,
user_id int, //fk of table_users.id
//more fields here
)
I plan to join the table_details and table_ot by using there user_id.
In the model that was generated by cake bake, the table_details is joining table_users and table_ot is table_users.
But table_details is NOT joining table_ot.
This is the content of table_details and table_ot.
$this->belongsTo('table_users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
also tried this one in the controller still does not work.
$Overtime = $this->table_ot->find()->all(array('joins' =>
array(
'table' => 'table_table_details',
'alias' => 'table_table_details',
'type' => 'full',
'foreignKey' => false,
'conditions'=> array('table_ot.user_id = table_table_details.user_id')
)
));
Any advise.. Help please
As you pointed in your question, you have already tables associations set up. So you can write your query like this:
$this->table_ot->find("all",[
"contain" => [
"table_users" => ["table_details"]
]
]);
After executing this query with, for example, toArray(), you can access your table_details record associated with table_ot like this:
$detailId = $results[0]->table_users->table_details->id;
As an alternative, I would suggest you to try joining these two tables like so:
//in initialize() method of your ot_table:
$this->hasOne("table_details")
->setForeignKey("user_id")
->setBindingKey("user_id");
All available options for each type of associations are listed here: https://book.cakephp.org/3.0/en/orm/associations.html
You have to add another field in table_ot to join with table_details according to cake convention. Because you have a foreign just to join with user table.
table_ot(
id int primary key,
user_id int, //fk of table_users.id
details_id int, //fk of table_details.id
//more fields here
)
Then add this code in table of table_ot
$this->belongsTo('table_details', [
'foreignKey' => 'details_id',
'joinType' => 'INNER'
]);

CakePHP Associations - hasMany but need to match column in 3rd table?

I am new to CakePHP (2.x) and am creating a post and comments feature. Everything works except I cannot figure out how to get the user's username out of the registrations (third) table (linked with "registration_id"). My associations currently look like:
class Article extends AppModel {
public $hasMany = array('ArticleComment');
public $belongsTo = array(
'ArticleRegistration' => array(
'className' => 'Registration',
'foreignKey' => 'Article.registration_id' //(doesn't work)
),
'ArticleCommentRegistration' => array(
'className' => 'Registration',
'foreignKey' => 'ArticleComment.registration_id' //(doesn't work)
)
);
class ArticleComment extends AppModel {
public $belongsTo = array('Registration','Article');
I am not sure if the associations from ArticleComment are being applied since it is being called through the Article model. I am retrieving the data by:
$this->set('articles', $this->Article->find('all', array('order' => 'Article.created desc', 'limit' => '3')));
I have tried a join and passing two separate variables for the articles and comments array but then I have to remove my associations which leads me to believe it's not proper coding.
Tables are:
articles
__________
id
registration_id
body
article_comments
__________
id
article_id
registration_id
body
registration
__________
id
username
I am fetching the information with:
$this->set('articles', $this->Article->find('all', array('order' => 'Article.created desc')));
TIA!
The generated SQL query from cakephp when you make a find() call is as follows
SELECT `Article`.`id`,
`Article`.`registration_id`,
`Article`.`body`,
`Registration`.`id`,
`Registration`.`username`
FROM `test`.`articles` AS `Article`
LEFT JOIN `test`.`registration` AS `Registration`
ON (`Article`.`registration_id` = `Registration`.`id`)
WHERE 1 = 1 LIMIT 20
So, in order to access the username you just need to write something like this in your view
$articles['Registration']['username'];
I recommend you to use the cakephp bake commands, I could make this proyect like in 10 minutes with them. And also I recommend using Netbeans as IDE, the cakephp plugin is awesome.
Here is a link to an example project that you can clone with git.

Deleting from join table in CakePHP

I am having a problem where I have tables users and teams, my relation is defined as below:
public $hasAndBelongsToMany = array(
'Teams' => array(
'className' => 'Team',
'joinTable' => 'teams_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'team_id',
'unique' => 'keepExisting',
'order' => array('name' => 'ASC')
)
);
Now the problem is that when I delete a user who for example belongs to team with id 1, all associations from teams_users that have id 1 disappear with it. In my unit tests I see that the delete query doesn't really care about the user_id and deletes all in my team :( following is the automated query:
DELETE `TeamsUser`
FROM `enterpriseappstore_test`.`teams_users` AS `TeamsUser`
WHERE `TeamsUser`.`team_id` = 1
This is the code responsible for deleting the user in Model:
$ok = $this->delete((int)$userId, false);
How do I delete only associations of the specific user, not team? So, from UsersController, I need to delete one user and his connections to all the teams that remain ... now for whatever reason, I am deleting user and all associations (connections from teams_users) where team_id is the same, not user_id
So in the end I had to do manual SQL ... not really proud of that but it is so far the only solution I have found. Very happy to award better solution:
$this->query('DELETE FROM `teams_users` WHERE `user_id` = '.(int)$userId.';');
$this->query('DELETE FROM `users` WHERE `id` = '.(int)$userId.';');
From the controller do this:
$ok = $this->User->delete((int)$userId);
Ensure that the foreign key from teams_users to users table has constraint ON DELETE CASCADE, f.ex.:
ALTER TABLE teams_users
ADD CONSTRAINT fk_users_teams
FOREIGN KEY(user_id)
REFERENCES users(id)
ON DELETE CASCADE;
http://www.mysqltutorial.org/mysql-on-delete-cascade/

Using self::STAT in relations to find the single latest related record

I am now having two tables tbl_user and tbl_log, and User and Log ActiveRecord classes respectively.
tbl_log
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`log_date` datetime NOT NULL,
`remarks` varchar(255) NOT NULL,
User class relations
public function relations() {
return array(
'rLog' => array(self::HAS_MANY, 'Log', 'user_id'),
);
}
What I am trying to achieve is to retrieve the latest record on tbl_log, that belongs to a certain user.
I have tried to add the following relation to the User class:
'lastLogDate' => array(self::STAT, 'Log', 'user_id', 'select'=>'log_date', 'order'=>'log_date DESC', 'group'=>'user_id', 'defaultValue'=>'N/A'),
so that I could retrieve the log_date from the latest record by calling something like:
$model = User::model()->findByPk($id);
echo $model->lastLogDate;
But then I realized it was actually not working properly. The log_date returned was always from the record with the smallest id on the tbl_log table, probably due to the behavior of GROUP BY and ORDER BY on a SQL query.
So now, I would like to know how (if possible) to achieve this by using a similar approach (i.e. using relations in the ActiveRecord class)? Thanks in advance.
The idea to go is using 'order' and 'limit', example:
'order'=>'log_date DESC',
'limit'=>1,
But you were wrong when use this type of relationship SELF::STAT, it is used to count the returned of records, not latest record
I don't usually use it that way, instead here is how I will:
In Log model, you should have:
public function relations()
{
return array(
'belongUser' => array(self::BELONGS_TO, 'User', 'user_id'),
}
And it would be simple like below
//get first found Log record of the user by given user_id and sort by log_date DESCENDANT
$lastLogDateRecord = Log::model()->with(array(
'belongUser' => array(
'condition' => 'user_id = :user_id',
'params' => array('user_id'=>$id) //$id is user_id param what user want
)
))->findByAttributes(array(), array('order' => 'log_date DESC'));

Naming convention and joins in CakePHP

Just a few days ago I found out about this miracle called CakePHP so I am pretty green to it.
I need to build a mail application, so I have followed the convention and created:
Database description:
Table of users <user_id (primary key), fname, lname>.
Table of mails <mail_id(primary key), from (foreign key to user_id), to (foreign key to user_id), content, opened>.
My questions:
1) According to the convention, a foreign key should be called related table+'_id'. How should I call the columns if there are two foreign keys that relate to the same table. Like from and to in the mails table.
2) I would like to do an inner JOIN the between the two tables.
Something like:
SELECT user_id, mail_id
FROM users
INNER JOIN mails
ON users.user_id =mails.to AND mails.opened=false.
But I have no clue how to do it.
When you need to do two relations to the same table, you will need to override the default convention. In your example, I would make 2 foreign keys. One named sender_id and one named recipient_id. Then you would join them in the Model like so:
<?php
class Mail extends AppModel {
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'UserSender' => array(
'className' => 'User',
'foreignKey' => 'sender_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'UserRecipient' => array(
'className' => 'User',
'foreignKey' => 'recipient_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
);
}
?>
Then to do your conditions, you would reference them like so:
<?php
$this->Mail->find(array('conditions'=>array('Mail.opened'=>false)));
?>
...and to filter on the sender and receiver, your conditions would look like:
<?php
$this->Mail->find(array('conditions'=>array('UserSender.some_field'=>$someValue,
'UserRecipient.some_field'=>$someValue)));
?>
I'm not an expert myself, but following info on the CakePHP site will help you further:
Multiple-relations-to-the-same-model

Categories