I'm working with CakePHP to develop a web application that has a few tables and some relationships between them. In this instance I have a series of meetings which reference a calendar year, a department and the relevant school however the model fails to grab the associated table information when data is returned from the controller to the view.
There is already existing $belongsTo relationships between staff and the department and school they belong to and I also managed to grab the associated meeting when the calendar array is returned on the calendar index but ideally I want to be able to list all the meetings with the names of calendars, departments and schools rather than the id field stored in the meetings table.
Here are my tables:
dbo.meetings
id (int) *primary key*
calendar_id (int)
department_id (int)
school_id (int)
created (datetime2(7))
modified (datetime2(7))
dbo.calendar
id (int) *primary key*
name (nvarchar(50))
startdate (datetime2(7))
enddate (datetime2(7))
created (datetime2(7))
modified (datetime2(7))
dbo.schools
id (int) *primary key*
name (nvarchar(255))
(other fields)
dbo.departments
id (int) *primary key*
name (nvarchar(255))
(other fields)
Here is the controller for Meetings:
<?php
App::uses('AppController','Controller');
class MeetingsController extends AppController {
public $helpers = array('Form');
public function beforeFilter() {
parent::beforeFilter();
}
public function index() {
$this->set('meetings', $this->Meeting->find('all'));
}
}
?>
Here is the model for Meeting:
<?php
App::uses('AppModel','Model');
class Meeting extends AppModel {
public $primaryKey = 'id';
public $recursive = -1;
public $belongsTo = array(
'Calendar' => array(
'className' => 'Calendar',
'foreignKey' => 'calendar_id'
),
'School' => array(
'className' => 'School',
'foreignKey' => 'school_id'
),
'Department' => array(
'className' => 'Department',
'foreignKey' => 'department_id'
)
);
}
?>
Currently the index.ctp file in /View/Meetings just contains <?php echo var_dump($meetings); ?> which is printing out the array until I can get the association working and then I will restyle it as required.
Here is what the array looks like atm:
array(1) {
[0]=>
array(1) {
["Meeting"]=>
array(6) {
["id"] => string(1) "1"
["calendar_id"] => string(1) "1"
["department_id"] => string(2) "33"
["school_id"] => string(1) "1"
["created"] => NULL
["modified"] => NULL
}
}
}
For some reason it just won't fetch the calendar, department and school details that it should and I want it to. Can anyone help?
Edit: Model for School
<?php
App::uses('AppModel','Model');
class School extends AppModel {
public $validate = array(
'name' => array(
'required' => array(
'rule' => 'notBlank',
'message' => 'A school name is required.'
)
)
);
}
?>
Make sure that you are enabling the Containable behaviour for your model's using public $actsAs = ['Containable'];:-
App::uses('AppModel','Model');
class Meeting extends AppModel {
public $recursive = -1;
public $actsAs = array('Containable');
public $belongsTo = array(
'Calendar',
'School',
'Department'
);
}
To apply Containable to all models set it in AppModel so that each model inherits the behaviour. Also, as long as you stick to CakePHP naming conventions you don't need to specify the className and foreignKey of your associations.
Now $this->Meeting->find('all') in your MeetingsController should return all the associated data along with the meeting. If you only want it to return some of the associated data you can pass the contain option to the find. For example:-
$this->Meeting->find(
'all',
array(
'contain' => array(
'School',
'Department'
)
)
);
Related
I have 3 tree like connected tables. Their schemas as follows:
Member{
//Some column
}
Transactions{
member_id :: foreign key of member table
//Some other column
}
TransactionItems{
transaction_id :: foreign key of Transaction table
//Some other column
}
I define models like this:
class Members extends AppModel {
public $primaryKey = 'id';
public $hasOne = array(
'Transactions' => array(
'className' => 'Transactions',
'foreignKey' => 'member_id',
'dependent' => true
)
);
}
class Transactions extends AppModel {
public $primaryKey = 'id';
public $belongTo = array('Members');
public $hasOne = array(
'TransactionItems' => array(
'className' => 'TransactionItems',
'foreignKey' => 'transaction_id',
'dependent' => true
)
);
}
class TransactionItems extends AppModel {
public $primaryKey = 'id';
public $belongTo = array('Transactions');
public $belongsTo = array(
'Transactions' => array(
'className' => 'Transactions',
'foreignKey' => 'transaction_id'
)
);
}
I have a Data Array which I want to save into database. My scheme is:
Array(
[Members] = [],//Array
[Transactions] = [],//Array
[TransactionItems] = []//Array
)
The problem is that whenever I run $this->Members->saveAll($data). It save data in Member and Transactions table. But do not create data in TransactionItems table. I want to save in all 3 tables at a time.
Any help would be grateful.
Second level (and above) associations must be nested, ie the data structure needs to be:
array(
'Members' => array(),
'Transactions' => array(
'TransactionItems' => array()
)
)
A bit awkward, but that's how it works in 2.x. You can always refer to the structure that is being returned when reading data, it needs to be the same when saving it.
Furthermore you must set the deep option to true in order to be able to save second level and above associations (by default only first level associations are being saved):
$this->Members->saveAll($data, array('deep' => true));
See also
Cookbook > Models > Saving Your Data > Model::saveAssociated()
I have a model class in CakePHP defined like this:
class Programme extends AppModel {
public $hasOne = array(
'ProgrammeLikes' => array(
'className' => 'ProgrammeLikes',
'fields' => array('likes'));
}
When retrieving my models from the database they are returned as an array with an array keyed to 'Programme' and a separate array keyed to 'ProgrammeLikes' (which contains the 'likes' value correctly). In order to reduce the changes necessary to existing code I want the 'likes' value to be within the 'Programme' array.
Is this possible?
Thanks in advance
Use virtualFields here to get this thing to be done.
class Programme extends AppModel {
public $hasOne = array(
'ProgrammeLikes' => array(
'className' => 'ProgrammeLikes',
'fields' => array('likes')
);
public $virtualFields = array(
'likes' => 'SELECT likes FROM programme_likes AS ProgrammeLikes WHERE ProgrammeLikes.id = Programme.programme_likes_id'
);
// Where programme_likes_id is the foriegnkey for Programme model
}
Note: I assumed programme_likes is your table name for ProgrammeLikes Model and programme_likes_id is the foriegnkey for
Programme Model, so you can arrange the query in your own way that suits your requirement.
My Category Model:
class Category extends AppModel {
public $displayField = 'name';
// public $actsAs = array('Containable');
public $hasAndBelongsToMany = array(
'Post' => array(
'className' => 'Post',
'joinTable' => 'categories_postss',
'foreignKey' => 'category_id',
'associationForeignKey' => 'post_id',
'unique' => 'keepExisting'
)
);
}
$params['contain'] = array('Post' => array(
'limit'=> 3));
pr($this->Category->find('first',$params)); exit;
It is fetching all Posts, irrespective of limit.
What I want to do:
I have this page where I ma listing all the categories and latest 5 posts related to it.
I want to limit the associated model to only 5 rows.
Any ideas?
Containable behavior is not in use
The most likely reason for this problem is that the containable behavior is not being used at all.
Compare, for the below code example:
$results = $this->Category->find('first', array(
'contain' => array(
'Post' => array(
'limit' => 3
)
)
));
Without containable behavior, it'll generate the following queries:
SELECT ... FROM `crud`.`categories` AS `Category` WHERE 1 = 1 LIMIT
SELECT ... FROM `crud`.`posts` AS `Post`
JOIN `crud`.`categories_posts` AS `CategoriesPost` ON (...)
With containable behavior, it'll generate the following queries:
SELECT ... FROM `crud`.`categories` AS `Category` WHERE 1 = 1 LIMIT
SELECT ... FROM `crud`.`posts` AS `Post`
JOIN `crud`.`categories_posts` AS `CategoriesPost` ON (...) LIMIT 3
Given this (and the code in the question) check that the AppModel has the containable behavior in $actsAs:
<?php
// app/Model/AppModel.php
class AppModel extends Model {
public $actsAs = array('Containable');
}
Limit always required?
Alternatively, or possibly in addition, you may prefer to put a limit in the association definition - To do so just define the 'limit' key:
class Category extends AppModel {
public $hasAndBelongsToMany = array(
'Post' => array(
'limit' => 100, // default to a high but usable number of results
)
);
}
the hasAndBelongsToMany relationship seems unnecessary to me. I think you only need Category hasMany Post and Post belongsTo Category relationships. Add category_id to the posts table. Make both models actAs containable.
Post Model
class Post extends AppModel {
public $actsAs = array('Containable');
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'category_id'
),
// ... more relationships
);
Category Model
class Category extends AppModel {
public $actsAs = array('Containable');
var $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'category_id'
),
// ... more relationships
);
Categories Controller
class CategoriesController extends AppController {
public $paginate = array(
'Category' => array(
'contain' => array(
'Post' => array(
'limit' => 3
), // end Post
) // end Category contain
) // end Category pagination
); // end pagination
public function index() {
// for paginated results
$this->set('categories', $this->paginate());
// for find results
$this->Category->contain(array(
'Post' => array(
'limit' => 3
)
));
$this->set('categories', $this->Category->find('all'));
}
I have these models:
class Prefix extends AppModel {
public $displayField = 'prefix';
public $hasMany = array(
'State' => array(
'className' => 'State',
'foreignKey' => 'prefix_id',
'dependent' => false,
),
);
}
class State extends AppModel {
public $displayField = 'name';
public $belongsTo = array(
'Prefix' => array(
'className' => 'Prefix',
'foreignKey' => 'prefix_id',
),
);
}
Then I have this admin_add method, from the automatic scaffolder:
public function admin_add() {
if ($this->request->is('post')) {
$this->Peefix->create();
if ($this->Prefix->save($this->request->data)) {
$this->redirect(array('action' => 'index'));
} else {
// Error message
}
}
$states = $this->Prefix->State->find('list');
$this->set(compact('states'));
}
I also have the list of them in my form:
<?php echo $this->Form->input('State', array('multiple' => 'checkbox', 'type' => 'select',)); ?>
Now I can set the States for the Prefix. However, when I submit the form, the selection disappears. It is not saved in the database.
What did I do wrong?
You linked the models as if there is only one state per prefix, and many prefixes "assigned" to one state. That means you cannot use 'multiple' => 'checkbox'. So either remove this or change model associations to HABTM.
First, both foreign keys for hasMany and belongsTo must be the same. If in the parent model you provided invoice_circle_id as the key, then the same must be provided in the child model also. Obviously, that field must exist in the child table. See this for more info http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
Second - you might want to use the saveAll() or saveAssociated() method for linked model data saving. Again - http://book.cakephp.org/2.0/en/models/saving-your-data.html contains all the ifnormation you need.
As for naming the input fields for hasMany, you name them like this:
$this->Form->input('ParentModel.fieldname');
$this->Form->input('ChildModel.0.fieldname');
I have a site develop in cakephp 2.0
I want to make a HABTM relation to the same model: A product can has more products.
I thinked to do in this mode into my model:
class Product extends AppModel {
public $name = 'Product';
public $useTable = 'products';
public $belongsTo = 'User';
public $actsAs = array('Containable');
public $hasAndBelongsToMany = array(
'Ingredient' => array(
'className' => 'Product',
'joinTable' => 'ingredients_products',
'foreignKey' => 'product_id',
'associationForeignKey' => 'ingredient_id',
'unique' => true
)
);
}
Is correct my relation? But have I to insert into my table products the field product_id and ingredient_id?
And how can I save my data with a form? I know how to save data with HABTM but I never done an HABTM to the same table.
Your relation is fine. What you have written will create a Product Model that can have any number of Ingredients and allows an Ingredient to belong to any number of Products.
When saving, you must simply treat the Ingredient as if it were another Model. The CakePHP example on saving HABTM works just as well as for associating the same model as with 2 different models: http://book.cakephp.org/2.0/en/models/saving-your-data.html.
So, if you're saving multiple Ingredients to a Product, your Array structure will look like this:
Array(
[0] => Array(
Product => Array(
id => 1
),
Ingredient => Array(
id => 18
)
),
1 => Array(
Product => Array(
id => 1
),
Ingredient => Array(
id => 23
)
)
// ...
)
It is up to you how you capture this in a form, but the form example used in the link provided above should manage this properly.