Deleting from join table in CakePHP - php

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/

Related

Laravel inserting by relationship to composite key table

I have these tables currently:
User table
id (primary key), name, email
User Model
protected $fillable = ['name', 'email'];
protected $visible = ['id','name','email'];
//Relationship
public function customAttributes()
{
return $this->hasMany('App\Models\UserAttribute');
}
UserAttribute Table
user_id, attribute_id, value //user_id and attribute_id is a composite key, both foreignkeys acting as primary keys establishing an unique combination
UserAttribute Model
protected $fillable = ['user_id', 'attribute_id','value'];
protected $visible = ['user_id', 'attribute_id','value'];
I'll use the following example to explain the issue:
$user = $this->user->create(['name' => 'admin', 'email' => 'admin#admin.com']);
//This works
$user->customAttributes()->save(new \App\Models\UserAttribute(['user_id' => $user->id, 'attribute_id' => 1, 'value' => 'Just a custom1']));
//This does not work
$user->customAttributes()->create([new \App\Models\UserAttribute(['user_id' => $user->id, 'attribute_id' => 1, 'value' => 'Just a custom1'])]);
I could just repeat the save for every custom that I want since it works, but I'm trying to figure out why create doesn't work.
The error I'm getting when I use create is (and yes, I've checked the record exists in the table that isn't listed here):
Cannot add or update a child row: a foreign key constraint fails (`testdatabase`.`user_attributes`,
CONSTRAINT `user_attributes_attribute_id_foreign` FOREIGN KEY (`attribute_id`) REFERENCES `attributes` (`id`))
This is the query it's trying to execute:
insert into `user_attributes` (`user_id`) values (1)
I'm just curious at why this doesn't work with create, I'm not sure if it's something related to this specific scenario (create to a composite key table by relationship). It's somewhat ignoring the value and attribute_id field in the query that is executing
try this:
$user->customAttributes()->create(['user_id' => $user->id, 'attribute_id' => 1, 'value' => 'Just a custom1']);
customAttributes() already returns you instance of UserAttribute model, you don't need to enject that dependency when you use create() method via that relation
your query should be like below;
$user->customAttributes()->insert([
[
'user_id' => $user->id,
'attribute_id' => 1,
'value' => 'Just a custom1'
],
[
'user_id' => $user->id,
'attribute_id' => 2,
'value' => 'Just a custom2'
],
]);

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'
]);

Associations not using foreign key

I have a model (AccountAgentDetail) that has 2 associations. One is a belongsTo (AccountUser) and the other is a hasOne(AccountProfile). The table for AccountAgent only has a FK relation to AccountUser. This model and the associated models are part of a plugin.
The issue I am seeing is that when the query is executed the join from AccountProfile to AccountAgentDetail is using the wrong association. It is using the id field of the AccountAgentDetail table instead of the fk field that I have defined in the AccountAgentDetail model.
This is the model that I am working with:
<?php
class AccountAgentDetail extends AccountModuleAppModel {
var $name = 'AccountAgentDetail';
var $primaryKey = 'agent_detail_id';
var $belongsTo = array(
'AccountUser' => array(
'className' => 'AccountModule.AccountUser',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
var $hasOne = array(
'AccountProfile' => array(
'className' => 'AccountModule.AccountProfile',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
public function getProspectiveAgents($count = 10)
{
return $this->find('all',
array(
'conditions'=>array('AccountAgentDetail.is_prospect'=>1),
'order'=>array('AccountAgentDetail.created_date DESC')
)
);
}
}
?>
This is the query that is executed when I call the method getProspectiveAgents. The issue I am seeing is in the second left join it is using AccountAgentDetail.agent_detail_id instead of AccountAgentDetail.user_id
SELECT
`AccountAgentDetail`.`agent_detail_id`,
`AccountAgentDetail`.`user_id`,
`AccountAgentDetail`.`is_prospect`,
`AccountAgentDetail`.`mls_id`,
`AccountAgentDetail`.`primary_office`,
`AccountAgentDetail`.`primary_board`,
`AccountAgentDetail`.`commission_plan`,
`AccountAgentDetail`.`referred_by`,
`AccountAgentDetail`.`referral_source`,
`AccountAgentDetail`.`previous_brokerage`,
`AccountAgentDetail`.`created_date`,
`AccountAgentDetail`.`last_modify_date`,
`AccountAgentDetail`.`created_by`,
`AccountAgentDetail`.`last_modifed_by`,
`AccountUser`.`user_id`,
`AccountUser`.`user_name`,
`AccountUser`.`user_pass`,
`AccountUser`.`user_status`,
`AccountUser`.`user_group`,
`AccountUser`.`instance_id`,
`AccountUser`.`is_logged_in`,
`AccountUser`.`is_visible`,
`AccountUser`.`created_by`,
`AccountUser`.`last_modified_by`,
`AccountUser`.`created_date`,
`AccountUser`.`last_modified_date`,
`AccountProfile`.`profile_id`,
`AccountProfile`.`user_id`,
`AccountProfile`.`first_name`,
`AccountProfile`.`middle_name`,
`AccountProfile`.`last_name`,
`AccountProfile`.`birth_date`,
`AccountProfile`.`ssn`,
`AccountProfile`.`employee_id`,
`AccountProfile`.`hire_date`,
`AccountProfile`.`sever_date`,
`AccountProfile`.`rehire_date`,
`AccountProfile`.`created_by`,
`AccountProfile`.`last_modified_by`,
`AccountProfile`.`created_date`,
`AccountProfile`.`last_modify_date`
FROM
`account_agent_details` AS `AccountAgentDetail`
LEFT JOIN `account_users` AS `AccountUser` ON(
`AccountAgentDetail`.`user_id` = `AccountUser`.`user_id`
)
LEFT JOIN `account_profiles` AS `AccountProfile` ON(
`AccountProfile`.`user_id` = `AccountAgentDetail`.`agent_detail_id`
)
WHERE
`AccountAgentDetail`.`is_prospect` = 1
ORDER BY
`AccountAgentDetail`.`created_date` DESC
You have decalared 'agent_detail_id' as primary key, it seems to me logical to select this as primary key for a join instead of user_id which is foreign key! Although I don't know exactly how this class works in the background I think you should work your model.
In your place I would work it in my mind out of the mvc-class-model context, thinking only under the E-Relational "frame".
Not sure about that...maybe you business requirements are such that you can share the primary key user_id, practically speaking having it both as foreign key and primary key in AccountAgentDetail if it is a ono-to-one.

DataMapper ORM for Codeigniter Relations

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;

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