How to Model Ternary Relationship in CakePhp? - database

Page table
(1)
|
|
(*)
User_Moderate_Page (*)----------- (1)Access_level table
(*)
|
|
(1)
User table
How do i Model such a ternary relationship in CakePhp?
User to Page can be modelled using the hasBelongtoMany Relationship. But User_Moderate_page is just an association table, should I even write a Model for User_Moderate_Page in Cake?

I'm not sure CakePHP accepts this, but what you should do is create the table with a primary key and the 3 foreign keys. Somewhat like this:
CREATE TABLE IF NOT EXISTS `mydb`.`access_levels_pages_users` (
`id` INT NOT NULL AUTO_INCREMENT ,
`page_id` INT NOT NULL ,
`access_level_id` INT NOT NULL ,
`user_id` INT NOT NULL ,
PRIMARY KEY (`id`) ,
INDEX `fk_access_levels_pages_users_pages` (`page_id` ASC) ,
INDEX `fk_access_levels_pages_users_access_levels1` (`access_level_id` ASC) ,
INDEX `fk_access_levels_pages_users_users1` (`user_id` ASC) ,
CONSTRAINT `fk_access_levels_pages_users_pages`
FOREIGN KEY (`page_id` )
REFERENCES `mydb`.`pages` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_access_levels_pages_users_access_levels1`
FOREIGN KEY (`access_level_id` )
REFERENCES `mydb`.`access_levels` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_access_levels_pages_users_users1`
FOREIGN KEY (`user_id` )
REFERENCES `mydb`.`users` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
Now, I'm not sure Cake will bake this table, so you might have to try and make the models by hand, this table would have belongsTo the 3 other tables. The other 3 tables non association tables will have to have a HMABTM relationship with the other 2, as in users HMABTM access_levels and pages and so on.
Again, not sure if it will work. I would suggest maybe trying to see if you can model it in a different manner.

Let me generalize it by describing it with generic tables:
Lets say you have 3 tables - firsts, seconds, thirds - each with primary keys 'id'.
let your join table be called 'firsts_seconds_thirds' with foreign keys to each main table:
first_id
second_id
third_id
<additional fields of this association table>
usually we define HABTM relationships between tables, in this case we need to create a cake model for the join table - lets call it FirstsSecondsThird (By cakePhp's naming conventions)
The relationship between models you need to define is:
First hasMany FirstsSecondsThird
Second hasMany FirstsSecondsThird
Third hasMany FirstsSecondsThird
FirstsSecondsThird belongsTo First,Second,Third
The need for this is explained here - Associations: Linking Models Together
Code for the same:
class First extends AppModel {
public $hasMany = array(
'FirstsSecondsThird' => array(
'className' => 'FirstsSecondsThird',
'foreignKey' => 'first_id'
)
);
}
//Same for classes 'Second' and 'Third'
class FirstsSecondsThird extends AppModel {
public $belongsTo = array(
'First' => array(
'className' => 'First',
'foreignKey' => 'first_id'
),
'Second' => array(
'className' => 'Second',
'foreignKey' => 'second_id'
),
'Third' => array(
'className' => 'Third',
'foreignKey' => 'third_id'
)
);
}
The models are perfectly setup, but now inserting/updating/deleting from the main/join table should be done correctly or they are of no use.
Model::saveMany() and Model::saveAssociated(), using the option 'deep' etc. need to be used. Read about it here. You also need to think about ON DELETE RESTRICT/CASCADE for these join tables and model them appropriately.

Look up ACL. It is complicated, but it is what you need (especially if you need the flexibility in setting up who can moderate what page)

Related

How to create more than one reference to a table in cakephp?

Let's say I have two tables:
CREATE TABLE drinks (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
morning_drink_id INT,
evening_drink_id INT
);
How can I make those drink_id references valid?
I’ve tried:
adding a view
CREATE VIEW morning_drinks AS SELECT * FROM drinks;
CREATE VIEW evening_drinks AS SELECT * FROM drinks;
adding foreign keys
FOREIGN KEY morning_drink_key (morning_drink_id) REFERENCES drinks(id),
FOREIGN KEY evening_drink_key (evening_drink_id) REFERENCES drinks(id)
bake craches in both cases... Is there a proper way?
public $belongsTo = array(
'Drink' => array(
'className' => 'Drink',
'foreignKey' => 'morning_drink_id'
)
);
this you can add to Users model. And this is how to do it.
edit:
Bake is expecting tables for your two foreign keys, and it’s complaining about them. I got the same error as you when I deleted the ‘morning_drinks’ view, else it created all. Add those two views and try again.

Join in cakephp not showing all the data

I'm trying to have this query
SELECT * FROM `rentprograms` AS `Rentprogram`
inner join `vehiclerentprograms` as `Vehiclerentprogram` on `Vehiclerentprogram`.`rentprogramid` = `Rentprogram`.`id`
inner join `vehicles` AS `Vehicle` ON `Vehicle`.`id` =`Vehiclerentprogram`.`vehicleid` WHERE `Vehicle`.`id` = 1
Code in CakePHP
$this->Rentprogram->find('all'), array(
'fields'=>array('*'),
'joins' => array(
array(
'table' => 'vehiclerentprograms',
'alias' => 'Vehiclerentprogram',
'type'=>'inner',
'conditions' => array(
'Vehiclerentprogram.rentprogramid' => 'Rentprogram.id',
)
),
array(
'table' => 'vehicles',
'alias' => 'Vehicle',
'type'=>'inner',
'conditions' => array(
'Vehicle.id' => 'Vehiclerentprogram.vehicleid',
)
)
),
);
But it only display the value of Rentprogram. How can i have all the fields related to Rentprogram, Vehicle, Vehiclerentprogram.
There's no value in using an MVC framework and do the dirty joins by hand. You'd better use Cake conventions, which lets you access Cake's libraries and tools which in turn speed up the development process quite a lot. In this case you have to setup models and associations between models (I hope you have heard of has-many, belongs-to, many-to-many and so on).
CakePHP ships with an invaluable DAO layer and a code generator called bake. Once you design the database schema, forget about SQL and think in terms of your business objects. First, create three tables in MySQL (I used a minimal set of fields and deduced the structure from your query):
CREATE TABLE `programs` (
`id` int(11) AUTO_INCREMENT,
`start` datetime DEFAULT NULL,
`end` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `vehicles` (
`id` int(11) AUTO_INCREMENT,
`model` varchar(128) DEFAULT NULL,
`plate` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `vehicle_programs` (
`id` int(11) AUTO_INCREMENT,
`program_id` int(11) DEFAULT NULL,
`vehicle_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
Then run the shell script:
Console/cake bake all
and select one table at a time (remember vehicle_programs must be the last one). This will generate all Models, Controllers and Views files for you. Then you can start filling your database with test data. Point your browser to http://host/vehicle_programs and put some vehicle and program in.
Finally I will show you how to retrieve all of the fields in one query. Suppose that you want to show everything when listing vehicle_programs. In the index() method of VehicleProgramsController you have to set $this->VehicleProgram->recursive to 1, so that it fetches related models fields as well. In the view index.ctp you'll now be able to access fields like
<?php
foreach ($vehiclePrograms as $vehicleProgram) {
echo $vehicleProgram['Program']['start'];
echo $vehicleProgram['VehicleProgram']['id'];
echo $vehicleProgram['Vehicle']['plate'];
}
Note if we hadn't set Model->recursive to 1, Cake wouldn't have fetched fields of related models (Vehicle and Program) for us.
Incidentally, I think not setting the fields key at all should do the trick, since Cake reads everything by default. However, the correct solution is using relationships between model classes - when you run bake it puts the following in Model/Vehicle.php:
class Vehicle extends AppModel {
public $hasMany = array(
'VehicleProgram' => array(
'className' => 'VehicleProgram',
'foreignKey' => 'vehicle_id',
'dependent' => false
)
);
}
and symmetric associations in Model/VehicleProgram.php
You can use this method
$this->Rentprogram->find('all'), array(
'fields' => array('Rentprogram.*', 'Vehicle.*', 'Vehiclerentprogram.*'), ...

CakePHP 2.1 hasAndBleongsToMany or hasMany through?

I'm new to the hasAndBelongsToMany relationships in CakePHP. I'm trying to figure out the best way to implement the following scenario. Do I use hasAndBelongsToMany or hasMany through?
In my application I am creating Repair Orders. The Repair Orders have Op Codes, which are assigned to employees. There can be more than one Op Code per Repair Order. So, it's likely I'll have something similar to the following:
RepairOrder1 - OpCode1 completed by Employee1, OpCode2 completed by Employee2
From what I read in the manual, the reason you can't use HABTM relationships for this type of situation is that when saving the data, the original information is removed first. However, in 2.1 there is a 'unique' key that you can set to have it keep the existing information first. So do I create a HABTM or a hasMany through?
EDIT
Here's my schema:
/* repair_orders */
CREATE TABLE repair_orders (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
number VARCHAR(16), //Auto-generated repair order number
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);
/* Employees */
CREATE TABLE employees (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(16),
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);
/* op_codes */
CREATE TABLE op_codes (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(16),
sale_material DECIMAL(10,2),
cost_material DECIMAL(10,2),
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);
I am trying to assign more than one op code to the repair orders, and also keep track of which employee did that specific op code. So I can have a repair order with 3 op codes, each with a different employee. So I think that requires another table, something like:
/* repair_order_assignments */
CREATE TABLE repair_order_assignments (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
repair_order_id INT(16),
op_code_id INT(16),
employee_id INT(16)
);
So my associations would be:
Repair_Order_Assignment hasMany Employee, Op_Code
Employee belongsTo Repair_Order_Assignment
Op_Code belongsTo Repair_Order_Assignment
Repair_Order_Assignment hasAndBelongsToMany Repair_Order
Repair_Order hasAndBelongsToMany Repair_Order_Assignment
Then according to the manual, when hasAndBelongsToMany associations are saved, the association is deleted first. You would lose the extra data in the columns as it is not replaced in the new insert. However, it goes on to note that in 2.1 there is a unique variable that can be set to save the extra data. Will this setup work with the unique key or should I use a hasMany through approach?
I would think you'd use neither in this case:
OpCode belongsTo RepairOrder
OpCode belongsTo Employee
RepairOrder hasMany OpCode
Employee hasMany OpCode
When retrieving your data, use CakePHP's Containable behavior:
$this->RepairOrder->find('all',
'contain' => array(
'OpCode' => array(
'Employee'
)
)
);

How to give 3 relations to same table in Cakephp

Hi i am new in cake php and can't solve the problem. The problem is I have a table like;
id varchar(16)
parent_id varchar(16)
text text
user_id bigint(20)
is_deleted_by_user bit(1)
is_deleted_by_us bit(1)
who_deleted bigint(20)
who_answered bigint(20)
modified_at datetime
created_at datetime
in this table i want to give relations between users table and user_id, who_deleted, who_answered. I mean user_id, who_deleted and who_answered are one user id. How can i give relations between users table and this table?
It's relatively easy to create multiple relationships to the same model. There's a section of the documentation dedicated to it. Here's how I've done it for a Resource model that has multiple fields associated with a Binary model:
class Resource extends AppModel {
public $belongsTo = array (
'PDF' => array (
'className' => 'Binary',
'foreignKey' => 'pdf_file_id'
),
'MSWord' => array (
'className' => 'Binary',
'foreignKey' => 'msword_file_id'
)
);
... other class code ...
}
The resources table contains pdf_file_id and msword_file_id fields which each reference a Binary record.
Hope that helps.

How can I model an is-a relationship with DBIx::Class?

With the following (simplified) MySQL table definitions:
create table items (
item_id int unsigned auto_increment primary key,
purchase_date date
) engine = innodb;
create table computers (
item_id int unsigned primary key,
processor_type varchar(50),
foreign key item_fk (item_id) references items (item_id)
on update restrict on delete cascade
) engine = innodb;
create table printers (
item_id int unsigned primary key,
is_duplex boolean,
foreign key item_fk (item_id) references items (item_id)
on update restrict on delete cascade
) engine = innodb;
Being new to DBIx::Class, I would like to model an inheritance relationship between database entities (computers and printers both being items), but with the provided belongs_to relationship type, this seems to be awkward, because the association with the base class is not hidden, so one must still manually create entities for both classes, and access to base class attributes in the derived classes is different from accessing their own attributes.
Is there an elegant solution which would allow me to say:
$printer = $printer_rs->create({purchase_date => $date, is_duplex => 0});
or (on a fetched printer row):
$date = $printer->purchase_date;
$duplex = $printer->is_duplex;
?
You can use the proxy attribute on a relationship to enable the accessors -- it's documented in add_relationship of DBIx::Class::Relationship::Base and you can use it with belongs_to like:
__PACKAGE__->belongs_to(
'item' => 'MyApp::Schema::Item',
'item_id',
{ proxy => [ qw/purchase_date/ ] }
);
which will make all of your Printer objects have purchase_date accessors that refer to the associated Item object.
As for create, you can't do it without overriding new_result, which is actually pretty easy. You just have to take advantage of the create-from-related behavior to turn
->create({
is_duplex => 1,
purchase_date => $dt,
})
into
->create({
is_duplex => 1,
item => {
purchase_date => $dt,
},
})
Or you could just foist the knowledge of what columns are in item off on your users and have them provide that hashref directly ;)

Resources