I am new in CakePHP, but I use Rails. I would like to do something like this in CakePHP:
class Manager < ActiveRecord::Base
has_many :employees
end
and ask then the object like this:
m = Manager.find(1)
# Sends SQL query SELECT COUNT * FROM EMPLOYEES WHERE MANAGER_ID = 1
count = m.employees.count
# Sends SQL query SELECT * FROM EMPLOYEES WHERE MANAGER_ID = 1
m.employees.each do |e|
puts e.name
end
I have this code in CakePHP...
class Manager extends AppModel {
public $hasMany = array(
'Employee' => array(
'className' => 'Employee',
'order' => 'Employee.created DESC'
)
);
}
class Employee extends AppModel {
public $belongsTo = 'Manager';
}
How can I do implementation of these (above) functionality (which is in RoR easy made by its ORM) in CakePHP?
Thanks for help...
Myth Rush
Your question boils down to "How can I retrieve the Manager with id=1 and find his Employees.
In CakePHP you would issue the following find query to retrieve the desired manager entry:
$manager = $this->Manager->findById(1);
// or
$manager = $this->Manager->find('first', array(
'conditions' => array(
'Manager.id' => 1
)
);
The above find calls fetch the manager with id=1 from the database and because you set up the relationship Manager hasMany Employee, the result will also contain all employees for the manager ordered by Employee.created DESC.
The resultset would look something like this:
Array
(
[Manager] => Array
(
[id] => 1
[field1] => value1
)
[Employee] => Array
(
[0] => Array
(
[id] => 2
[manager_id] => 1
[name] => Bar
)
[1] => Array
(
[id] => 1
[manager_id] => 1
[name] => Foo
)
)
)
As you can see, CakePHP uses an array format for returned results and not an object as in Ruby on Rails. Therefore you have to access your related data within the resulting array.
Another important difference between CakePHP and Ruby on Rails(RoR) is, that in CakePHP the queries to the db are executed the moment you call them, whereas in RoR they are lazily executed the moment you try to access the results.
To complement your RoR example for accessing employees here is the CakePHP version:
$employee_count = count($manager['Employee']);
foreach ($manager['Employee'] as $e) {
echo $e['name'];
}
I hope this clears up some confusion.
I think you are looking for the containable behavior of CakePHP.
It is important to attach the containable behavior to your model(s), otherwise it won't work.
Related
I'm having trouble saving a many to many relation. My database schema are as below:-
articles
- id (PK)
- content
tags
- id (PK)
- name
article_tag
- article_id (PK)
- tag_id (PK)
My Article model has the following relation:-
'tags' => array(self::MANY_MANY, 'Tag', 'article_tag(tag_id, article_id)'),
However, when I saw using a Yii behavior, I get the following error:-
Table "tags" does not have a column named "article_tag(tag_id, article_id)".
I have run through everything from schema to relation and I can't seem to figure out the problem. I have tried using other extensions as well and none of them seem to save to the m:n table.
Am I missing something here?
Additional Information
Here is my CManyManyRelation Object; the foreignKey just doesn't look right.
CManyManyRelation Object ( [limit] => -1 [offset] => -1 [index] => [through] => [joinType] => LEFT OUTER JOIN [on] => [alias] => [with] => Array ( ) [together] => [scopes] => [name] => tags [className] => Tag [foreignKey] => restaurant_tag(restaurant_id, tag_id) [select] => * [condition] => [params] => Array ( ) [group] => [join] => [having] => [order] => [_e:CComponent:private] => [_m:CComponent:private] => )
In the Many-Many relation in the Article class you should put first the id of the Article and then the id of the tag:
'tags' => array(self::MANY_MANY, 'Tag', 'article_tag(article_id, tag_id)'),
Then in the class Tag you should have:
'articles' => array(self::MANY_MANY, 'Article', 'article_tag(tag_id, article_id)'),
Edit: My bad I didn't realize it was saving the related model that causes the problem.
There is no build-in possibility to save the related record in Yii
For me the best solution to save a related models is the extension activerecord-relation-behavior. The extension will be handling all the HAS_MANY and MANY_MANY relations.
I've been using yii-manymanyactiverecord extension to help simplify the process, you can find it here: http://www.yiiframework.com/extension/yii-manymanyactiverecord/
My title will look like naive but I have to say I read/searched/tested everything possible, but my find() method don't implement the JOIN to related tables in the SQL query. I used it several times in other projects without problems but here...
Here my 2 models (nothing special but the manual definition of the related model) :
class Pflanzen extends AppModel {
public $useTable = 'pflanzen';
public $hasAndBelongsToMany = array(
'Herbar' => array(
'order'=>'Herbar.order ASC',
'joinTable' => 'herbar_pflanzen',
'foreignKey' => 'pflanzen_id',
'associationForeignKey' => 'herbar_id')
);
}
class Herbar extends AppModel {
public $useTable = 'herbar';
public $hasAndBelongsToMany = array(
'Pflanzen' => array('joinTable' => 'herbar_pflanzen',
'foreignKey' => 'herbar_id',
'associationForeignKey' => 'pflanzen_id')
)
}
Here my query in the "Herbar" controller (can't be more normal...) :
$pflanzen = $this->Herbar->Pflanzen->find('all',array(
'fields'=>array('Herbar.name','Pflanzen.linkplatter'),
'conditions' => array('Pflanzen.linkplatter' => true),
'order' => 'Herbar.name',
'limit' => 10,
'recursive'=>2)
);
$this->set('pflanzen',$pflanzen);
and the resulting error in the view :
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Herbar.name' in 'field list'
SQL Query: SELECT `Herbar`.`name`, `Pflanzen`.`linkplatter`, `Pflanzen`.`id` FROM `burgerbib`.`platter_pflanzen` AS `Pflanzen` WHERE `Pflanzen`.`linkplatter` = '1' ORDER BY `Herbar`.`name` ASC LIMIT 10
You can see that their is no JOIN in the SQL. Why ?? What do I wrong ?
I would really appreciate your help as I'm searching for hours and do no more see any solutions and didn't find nothing using google. Thanks in advance !!
HABTM doesn't make joined queries, it makes a query for all base records and more queries as needed for each relationship to fill the array. Your condition assumes a join, hence the error.
You can force a join using the 'joins' parameter. http://book.cakephp.org/1.2/en/view/872/Joining-tables
In the End, the better way of doing this is using the containable behaviour. Force Join is only useful when the containable behavior don't respond to the need :
http://book.cakephp.org/2.0/fr/core-libraries/behaviors/containable.html#using-containable
I have two tables tbl_business and business_contacts of the following structure:
tbl_business
---
business_id (PK)
othercolumns
and
business_contacts
---
contact_id (PK)
business_id
othercolumns
The scenario is that one business row has many contacts. I am using cGridview using gii's CRUD generator and needed to display firstname and lastname from business_contacts (one of multiple possible rows in the table) for each tbl_business record.
As far as I understand, I've updated the relation function in tbl_business's model as:
'businesscontacts' => array(self::HAS_MANY,'BusinessContact','business_id','select' => 'contact_firstname, contact_lastname')
and for the same, a contact relation is defined in the business_contacts' model as:
'contactbusiness' => array(self::BELONGS_TO,'BusinessContact','business_id')
I expected that would work for pulling related records so that I can have something in the grid like, business_id, contact_firstname, contact_lastname , ... otherbusinesstablecolumns .. but I'm only getting blank values under firstname and lastname .. could someone please help me understand the error? :(
So you are trying to display a table of Businesses (tbl_business) using CGridView? And in each Business's row you want to list multiple Contacts (business_contacts)?
CGridView does not support displaying HAS_MANY relations by default. CGridView makes it easy to list which Business a Contact BELONGS_TO (i.e. you can use a column name like contactbusiness.business_id), but not all of the Contacts that are in a business.
You can do it yourself though, by customizing a CDataColumn. (Note: this will not allow you to sort and filter the column, just view. You'll have to do a lot more work in to get those working.)
First, in your Business model, add a method like this to print out all of the contacts:
public function contactsToString() {
$return = '';
foreach ($this->businesscontacts as $contact) {
$return .= $contact->contact_firstname.' '.$contact->contact_firstname.'<br />';
}
return $return;
}
(EDIT: Or do this to print out just the first contact):
public function contactsToString() {
if($firstContact = array_shift($this->businesscontacts)) {
return $firstContact->contact_firstname.' '.$firstContact->contact_firstname;
}
return '';
}
Then make a new column in your grid and fill it with this data like so:
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'business-grid',
'dataProvider'=>$model->yourDataProviderFunction(),
'columns'=>
'business_id',
array(
'header'=>'Business Contacts', // give new column a header
'type'=>'HTML', // set it to manual HTML
'value'=>'$data->contactsToString()' // here is where you call the new function
),
// other columns
)); ?>
EDIT2: Yet another way of doing this, if you just want to print out ONE of a HAS_MANY relation, would be to set up a new (additional) HAS_ONE relation for the same table:
public function relations()
{
return array(
'businesscontacts' => array(self::HAS_MANY,'BusinessContact','business_id','select' => 'contact_firstname, contact_lastname') // original
'firstBusinesscontact' => array(self::HAS_ONE, 'BusinessContact', 'business_id'), // the new relation
);
}
Then, in your CGridView you can just set up a column like so:
'columns'=>array(
'firstBusinesscontact.contact_firstname',
),
Getting only the first contact could be achieved like this also:
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'business-grid',
'dataProvider'=>$model->yourDataProviderFunction(),
'columns'=>
'business_id',
//....
array(
'name' => 'contacts.contact_firstname',
'value' => '$data->contacts[0]->contact_firstname', // <------------------------
'type' => 'raw'
);
//....
),
taxonomies
-id
-name
taxonomy_type
-taxonomy_id
-type_id
I've configured two models:
class Model_Taxonomy{
protected $_has_many = array('types'=>array());
}
class Model_Taxonomy_Type{
protected $_belongs_to = array('taxonomy' => array());
}
*Please note that taxonomy_type is not a pivot table.*
A taxonomy can have multiple types associated.
Then, what I'm trying to do is get all taxonomies that belong to a given type id.
This is would be the SQL query I would execute:
SELECT * FROM taxonomies, taxonomy_type WHERE taxonomy_type.type_id='X' AND taxonomies.id=taxonomy_type.taxonomy_id
I've tried this:
$taxonomies = ORM::factory('taxonomy')
->where('type_id','=',$type_id)
->find_all();
Obviously this doesn't work, but I can't find info about how execute this kind of queries so I have no clue.
class Model_Taxonomy{
protected $_belongs_to = array(
'types' => array(
'model' => 'Taxonomy_Type',
'foreign_key' => 'taxonomy_id'
)
);
}
class Model_Taxonomy_Type{
protected $_has_many = array(
'taxonomies' => array(
'model' => 'Taxonomy',
'foreign_key' => 'taxonomy_id'
)
);
}
And use some like that:
$type = ORM::factory('taxonomy_type')
->where('type_id', '=', $type_id)
->find();
if( ! $type->taxonomies->loaded())
{
types->taxonomies->find_all();
}
type_id column is a PK of taxonomy_type table, am I right?
So, you have one (unique) taxonomy_type record, and only one related taxonomy object (because of belongs_to relationship). Instead of your:
get all taxonomies that belong to a
given type id
it will be a
get taxonomy for a given type id
I have two tables:
Contestant and Votes
Contestant hasMany Votes
I've tried doing a count(Vote.id) as Votes so I can place it on the
recordset and just paginate them but I have no idea where to place it.
I did it on the fields array but it gave me the total count of votes
regardless of the contestant they belong to.
The votes are linked together in the Contestant recordset so what I
did was on my view I did a count($contestant[Vote]) but this can't be
paginated and my client wants to be able to sort the contestants by
votes.
Is there a way I can do something like this on my view?:
sort('Votes', 'count(Vote)'); ?>
Or do I have to create a query which does a count for all the votes
where Contestant.id = Votes.contestant_id ?
Controller Contestant:
function index() {
$page = 'Contestants';
$this->set('page', $page);
$this->paginate =
array(
'order' => 'id ASC',
'contain' => array(
'Vote' => array(
'fields' => array("Vote.contestant_id",'Vote.id')
)
)
$conditions ["Contestant.active"] = 1;
$this->set('contestants', $this->paginate('Contestant',
$conditions));
}
Check out deceze's response in this question: CakePHP mathematic-calculation field?
Essentially you want to do something like this I'm guessing:
'contain' => array(
'Vote' => array(
'fields' => array('SUM(Vote.id) AS Contestant__votes'),
'group' => array('Vote.contestant_id'),
)
)
Since cakephp doesn't support group by in containable behavior I tried a different approach. Create the paginate var for the vote model instead (All of this is done in the Contestants Controller):
var $paginate = array(
'Vote'=>array(
'limit'=>5,
'fields' => array(
'Contestant.*, count(Vote.contestant_id) as Contestant_votes, Vote.id'
),
'group' => array(
'Vote.contestant_id'
),
'order' => array(
'Contestant_votes Desc'
)
),
'Contestant'=>array(
'limit'=>5,
'order' => array(
'Contestant.id Desc'
)
)
);
And now in my controller I do the following:
function index() {
$page = 'Contestants';
$this->set('page', $page);
$conditions ["Contestant.active"] = 1;
$this->set('contestants', $this->paginate($this->Contestant->Vote,$conditions));
}
Now the contestants are ordered by their total vote tally, although I still can't figure how to place the Contestant_votes as a paginator variable since in the record set it's in a array of it's own and not in any of the model arrays used to paginate.
Thanks Matt Huggins your approach was the one that led me to this solution.
Addition: Do you also want to sort by Votes (total votes) ascending or descending? If yes, you can not do it easily by the default pagination method of cakephp.
For that you need a little tweak. Here is the details about this trick: CakePHP Advanced Pagination – sort by derived field
Hope you'd find it helpful.
Thanks
Adnan
For the specific relationship you define, your needs are well-served by counter-caching.
You will need to define a new field in your contestants table: vote_count. Then, in your Votes model, you'll need to update the $belongsTo definition slightly:
class Votes extends AppModel
{
var $belongsTo = array(
'Contestant' => array( 'counterCache' => true )
);
}
Now, anytime a Vote record is saved, the vote_count field of the parent Contestant will be updated. Now you can simply sort by Contestant.vote_count as you would any other Contestant field:
class ContestantsController extends AppController
{
// Controller stuff that comes before...
function index()
{
$this->paginate = array( 'Contestant' => array(
'conditions' => array( 'Contestant.active' => 1 ),
'order' => array( 'Contestant.vote_count' => 'DESC' ),
));
$contestants = $this->paginate('Contestants');
$this->set( compact('contestants'));
}
// Controller stuff that comes after...
}