I have the following tables:
- restaurants (restaurant.id, restaurant.name)
- menus (menu.id, menu.name, menu.active, menu.restaurant_id)
I want to have a list with all restaurants with the active menus (menu.active = true):
- restaurant2
- menu1
- menu4
- restaurant5
-menu3
- restaurant19
- menu34
- menu33
My first idea was something like this:
$options['contain'] = array(
'Menu' => array(
'conditions' => $menuParams //array('Menu.active' => '1') //$menuParams
)
);
This doen't work becaus all restaurants will be listed. I want to have only restaurants with active menus.
Next idea: using join
$options['joins'] = array(
array('table' => 'menus',
'alias' => 'Menu',
'type' => 'RIGHT',
'conditions' => array(
'Menu.restaurant_id = Restaurant.id',
)
)
);
Not good, because, I don't have the ordered list I want. I need the menus grouped by the restaurant. Look above.
Is it the right way to make a join with restaurants and menus(active = true) and then using the contain to get the ordered list? I think that could work but I think also there is an easier way, right?
Any help is welcome! Thank you.
If you are using the ORM machinery from CakePHP, you should have a Restaurant model and a Menu model to describe each table. In the Restaurant model, you should have a $hasMany = "Menu" field, and in menu a $belongsTo = "Restaurant" field (assuming the model names are Menu and Restaurant).
From that point, doing queries using ORM is fairly straightforward:
$this->Restaurant->recursive = 1; // grab the menus
$conditions = array('Menu.active' => '1'); // restrict to active menus only
$this->Restaurant->find('all', array('conditions' => $conditions));
The above in the ad-hoc method of the Restaurant controller should retrieve the rows as an array of Restaurant objects, each bundled with an array of active Menu.
Now I found the easy and clean solution for my concern! Yeah!
First I had to unbind the bindings and then I had to make a new binding with the condition. It works like a charm. Here is the code:
$this->unbindModel(array('hasMany' => array('Menu')));
$this->bindModel(array('hasMany'=>array(
'Menu'=>array(
'foreignKey' => 'restaurant_id',
'conditions' => array(
'Menu.active' => 1
)
)
)));
I thank you all for your answers!
Related
I have the following code in my projects controller's index action
I achieved it via custom queries
1- it is good to use custom queries?
2- how can I write the following queries using cakephp functions joins contains etc
3- which method will be good?
my tables are as below
portfolio_projects [id,name,....,user_id,job_id,manymore_id]
portfolio_images [id,project_id,image,orders,status,.....]
portfolio_tags [id,tag,....]
portfolio_project_tags[id,project_id,tag_id]
and query is as below. I did this to fetch only needed data that is projects with its images and tags but not project's (user,job and others)
there are other tables linked to tags, images tables too but I do not need that data here.
$this->Project->recursive = -1;
$projects = $this->Project->find('all',array(
'conditions' => array(
'Project.user_id' => $this->Auth->user('id')
)
));
foreach($projects as $key=>$project)
{
//fetching project related tags starts here
$q="select * from portfolio_project_tags where 0";
$q="select tag.id as id,tag.tag from portfolio_project_tags as p left outer join portfolio_tags as tag on p.tag_id=tag.id where p.project_id=".$project['Project']['id'];
$tags = $this->Project->query($q);
$projects[$key]['Project']['tags']=$tags;
//fetching project related tags ends here
//fetching project related images starts here
$q2="select * from portfolio_images where 0";
$q2="select img.id as id,img.title as title, img.image as image from portfolio_images as img where img.project_id=".$project['Project']['id']." and img.status='Publish' order by orders";
$snaps = $this->Project->query($q2);
$projects[$key]['Project']['snaps']=$snaps;
//fetching project related images ends here
}
$this->set('projects',$projects);
You have 3 possibilities (at least) to retrieve data from a join table in CakePHP, that you should consider in the following order:
Containable Behaviour
CakePHP join query
Custom query
In your example, only the q1 request need a join because the q2 can be express as:
$this->PorfolioImage->find('all', array(
'fields' => array('PortfolioImage.id, PortfolioImage.title, PortfolioImage.image'),
'conditions' => array(
'PortfolioImage.project_id' => $project['Project']['id']
)
));
The containable behaviour
In your Project model, you could add information to the related models, such as:
class Model extends AppModel {
public $actAs = array('Containable');
public $hasMany = array('PortfolioImage');
public $belongsTo = array('PortfolioTag') ;
}
Then, when you retrieve a Project, you have access to the related PortfolioImage and Tag, for example:
debug($this->Project->read(null, 1)) ;
Would output:
Array(
'Project' => array(
'id' => 1,
/* other fields */
),
'PortfolioTag' => array(
'id' => /* */,
'tag' => /* */
),
'PortfolioImage' => array(
0 => array(
'id' => /* */,
/* other fields */
),
1 => array(/* */)
)
)
You just need to be carefull on the naming of foreign key, the CakePHP documentation about relationship is really easy to read.
CakePHP join query
You can do a join query in CakePHP find method:
$this->Project->find('first', array(
'fields' => array('Tag.id', 'Tag.title'),
'joins' => array(
'table' => 'portfolio_tags,
'alias' => 'PortfolioTag',
'type' => 'LEFT OUTER',
'conditions' => array('PortfolioTag.id = Project.tag_id')
)
)) ;
I'm really not used to CakePHP join queries (the Containable behaviour is often sufficient), but you'll find lot of information in the CakePHP documentation.
Custom queries
You should really not use custom queries except if you really know what you're doing because you have to take query of almost anything (sql injection at first place). At least, when doing custom queries, use Prepared Statements (see information at the end of this link).
I'm trying to create an AJAX form whereby the content of a select field populates based on the choice of a preceding select field (you see this a lot with 'country' populating 'state/province'). In my case, I want users to be able to choose their province only if active accounts exist in it.
The Javascript I can write no problem. Fetching the data is where I'm... not so much stuck as doing too much work. CakePHP likes to build select fields with options in an array of the form
$options = array(select_option_value => display_text)
My strategy, though functional, must be more convoluted than cake intended (this a is segment of a controller method).
$provinceData = $this->Account->find('all',array('recursive' => 0,
'joins' => array(
array(
'table' => 'provinces',
'type' => 'LEFT',
'conditions' => array('Account.province_id = provinces.id')
)),
'fields'=>array('provinces.id', 'provinces.name', 'provinces.abbrev'),
'conditions' => array('registration > 2')));
$provinces = array();
foreach($provinceData as $pd) {
/*note: lowercase, plural below b/c can't get 'alias' => 'Province'
to work in joins array above : ( */
$id = $pd['provinces']['id'];
$name = $pd['provinces']['name'];
$provinces[$id] = $name;
}
$this->set(compact('provinces'));
Can anyone point out a more appropriate way to do this? I assume there must be a MySQL query that can do this, but I'm pretty bad at writing elaborate MySQL queries in the first place, let alone via Cake's convention (and, for you MySQL gurus out there, I'm happy to do this from a Model->query(//MySQL code) call instead!
Any and all help truly appreciated.
Assuming the relationship Account belongsTo Province you can try this code:
$accounts = $this->Account->find(
'all',
array(
'fields' => array('Account.province_id', 'Province.name'),
'conditions' => array('Account.registration > 2'),
'group' => 'Account.province_id'
)
);
$provinces = Hash::combine($accounts, '{n}.Account.province_id', '{n}.Province.name');
$this->set(compact('provinces'));
edit: missed bracket and a period instead of an underscore . Now should work
In my CakePHP site, I want to make a drop-down list of all Venues, and any Restaurants that have is_venue=1.
I've tried this in my events_controller:
$venueOptions = array(
'fields' => array('id', 'name_address'),
'order' => array('name'),
'join' => array(
array(
'table' => 'restaurants',
'alias' => 'Restaurants',
'type' => 'inner',
'fields' => array('id', 'name'),
'foreignKey' => false,
'conditions' => array('restaurants.is_venue = 1')
)
),
);
$venues = $this->Event->Venue->find('list', $venueOptions);
But it appears to still just be getting the venues. I don't really need an association between the two, since their associations will both be with an event, not each other.
Where have I gone wrong? Am I close, but just need to tweak this code, or am I just all-together doing it wrong?
I think you could do something along the lines of:
<?php
....
$v = $this->Venue->find( 'list' );
$r = $this->Restaurant->find( 'list' );
$venues = Set::merge( $v, $r );
natcasesort( $venues );
// print_r( $venues );
$this->set( 'venues', $venues );
...
?>
Which is quite like the code above - I just use the Set class and make sure to Controller::set the variable to the view.
Also added some basic sorting to show you one option even though array sorting has nothing really specific to do with CakePHP.
Also fixed some bad variable names where I had originally used $venues, and $restaurants - changed to be consistently $v and $r.
Join will not work if there's no relation between. Venue and Restaurant. You should call them separately and merge the results
$venues = $this->Event->Venue->find('list', $venueOptions);
$restaurants = $this->Event->Restaurant->find('list', array('conditions' => array('is_venue' => '1')));
$results = array_merge($venues, $restaurants);
// sort results
asort($results);
I have thee following simple model:
Item belongsTo CatalogItem
CatalogItem hasMany Item, and belongsTo Section
Section hasMany CatalogItem
I'm trying to get counts of items, grouped by catalogitem, for a certain section-
the equivalent of:
SELECT catalogitem.id, count(*) FROM section LEFT JOIN catalogitem ON section.id=catalogitem.section_id LEFT JOIN item ON item.catalogitem_id=catalogitem.id WHERE section.id=5 GROUP BY catalogitem.id
So simple in sql, yet I can't get it to work with cake models. Can anyone point as to how to do it with cake models, using the model->find?
I can't get it to group by correctly or join correctly on 3 tables :(
Edit:
highly prefer to get the info in single query
Here's a longer way, "cakeish" way:
class Item extends AppModel
{
/* snip */
var $virtualFields = array('item_count' => 'count(Item.id)');
function getCountForSection($sectionId)
{
$ca = $this->Catalogitem->find
(
'all',
array
(
'fields' => array('Catalogitem.id'),
'conditions' => array('Catalogitem.section_id' => $sectionId),
'recursive' => -1
)
);
$ca = Set::extract('/Catalogitem/id', $ca);
$ret = $this->find
(
'all',
array
(
'fields' => array('Item.catalogitem_id', 'item_count'),
'conditions' => array('Item.catalogitem_id' => $ca),
'group' => array('Item.catalogitem_id'),
'recursive' => -1
)
);
return $ret;
}
}
Then simply use it in your controller:
$ret = $this->Item->getCountForSection(1);
debug($ret);
How does it work:
Define a virtual field (cake 1.3+ only AFAIK) which will count items
Fetch all the Catalogitems belonging to a Section you're interested in
Use Set::extract() to get the Catalogitems in a simple array
Use the array of Catalogitems to filter Items while counting and grouping them
NB: You don't seem to be using Cake's naming conventions in your database. This may hurt you.
Sorry, in my first answer I somehow missed your GROUP BY requirement, which was the whole point of the question, I now realize. I haven't used this yet, but I came across it recently, and it looks like it might accomplish what you are looking for: Linkable Behavior.
http://planetcakephp.org/aggregator/items/891-linkable-behavior-taking-it-easy-in-your-db
Like Containable, but works with only right and left joins, produces much more compact queries and supports GROUP BY.
http://github.com/rafaelbandeira3/linkable
#azv
Would this work for you:
$section_id = 5;
$fields = array('CatalogItem.id as CatalogItemId', 'count(*) AS SectionCount');
$conditions = array('Section.id' => $section_id);
$joins = array(
array('table' => 'catalogitem',
'alias' => 'CatalogItem',
'type' => 'LEFT',
'conditions' => array('Section.id' => 'CatalogItem.section_id')
),
array('table' => 'item',
'alias' => 'Item',
'type' => 'LEFT',
'conditions' => array('Item.catalogitem_id' => 'CatalogItem.id')
));
$data = $this->Section->find('all',
array('fields' => $fields,
'conditions' => $conditions,
'joins' => $joins,
'group' => 'CatalogItem.id',
'recursive' => -1)
);
// access your data values
foreach ($data['Section'] as $i => $datarow) {
$catalogitem_id = $datarow['CatalogItemId'];
$section_count = $datarow['SectionCount'];
}
This way you are explicitly setting your joins and doing it all in one query. See here for more info on joins in Cake:
http://book.cakephp.org/view/1047/Joining-tables
Hope this helps. All the best,
-s_r
I have three models linked in this manner: Item->Order->Payment
Order hasMany Item
Order hasOne Payment
Now, I am paginating Items and want to add a condition in it to find only items of that order which has payment of a particular id. I hope that makes sense :P
I added the condition as:
array('Payment.id'=>$id)
but it doesn't work. Obviously cause Payment is not associated with Item.
So, how can I go about this?
I am new to cakephp, maybe I am completily wrong but as I understand it you can use other models in your controller with the $uses variable. First make a query on payment model to get your order id, than you can use this id to find the corresponding items.
$uses=array('Item','Order','Payment');
$order_id=$this->Payment->find('first',array('fields'=>'order_id','conditions'=>array('id'=>$payment_id)));
$items=$this->Item->find('all',array('conditions'=>array('order_id'=>$order_id)));
I hope it help.
Why don't you add a condition:
array('Order.payment_id'=>$id)
I think this should work.
If you specify that you want two levels of recursion this should work. Im assuming you have
in Payment.php
//recursion level 1
var $belongsTo = array('Order');
in Order.php
//recursion level 2
var $hasMany = array('Items')
You are right that for paginate to work you must query the model you wish to page and sort the lists by.
in PaymentController.php
//Query two levels deep, so the $payment['Order']['Item'][0-n] will be present
var $paginate = array('recursive' => 2);
Note this method does generate another query for each row to retrieve items.
Make sure the debug level in app/config/core.php is set to 2 to see the database calls.
1) You can use Containable behaviour, in which case you need to put this in your Item model:
var $actsAs = array('Containable');
and this into your Items controller:
$items = $this->Item->find('all'
, array (
'contain' => array('Order' => array('Payment'))
, 'conditions' => array('Payment.id' => $paymentId)
)
)
However I suspect that that will do a left join onto the Payments table (as its a hasMany relationship). So you won't filter Items in any way.
2) If you can't get contains to work then I often use explict joins (read this bakery article by nate on joins) in my find queries. So in your Items controller you'd have:
$items = $this->Item->find('all'
, array (
, 'joins' => array(
array(
'table' => 'payments'
, 'alias' => 'Payment'
, 'type' => 'INNER'
, 'conditions' => array(
'Option.id = Payment.option_id'
)
)
)
, 'conditions' => array('Payment.id' => $paymentId)
)
)
You may also need to specify the join onto the options table.