cakephp hasmany query issue - php

I have a three tables which have associations as follows:
listings hasmany bookings
listings hasmany special_prices
Tables:
bookings.id, bookings.start, bookings.stop, bookings.listing_id
special_prices.id, special_prices.start, special_prices.stop, special_prices.price, special_prices.listing_id,
And of course the Listings table.
The associations are all set up in the Model file courtesy of cake bake. Users are able to search all the listings according to area, a start date and a finish date. I'm trying get a set of results where only listings are returned if they don't have a start date which is BETWEEN the dates of any booking that listing might have.
In my search function I have:
$conditions = array(
"AND" => array(
"OR" => array(
"Listing.address_one LIKE" => "%".$area."%",
"Listing.address_two LIKE" => "%".$area."%",
"Listing.city LIKE" => "%".$area."%",
"Listing.postcode LIKE" => "%".$area."%",
"Listing.title LIKE" => "%".$area."%",
),
)
);
$this->Listing->bindModel(array('hasMany' => array('Booking' => array('conditions' =>array('Booking.checkin BETWEEN ? AND ?' => array($to, $from))))));
$data = $this->paginate('Listing', $conditions);
$this->set('data', $data);
I read here: http://ibndawood.com/2011/10/how-to-filter-hasmany-related-model-records-in-cakephp-when-using-find-function/ that the other option is to use
$this->Listing->hasMany['Booking']['conditions'] = array('Booking.checkin BETWEEN ? AND ?' => array($to, $from));
But unfortunately both these just return all the entries in the database which match the area but completely ignores the Bookings table it seems. I've tried making the condition
'Booking.id' => '5'
To test, but it ignores this too.
Can anyone shed some light on this for me. There's not too many examples in the documentation and what's there ain't working for me.
EDIT
I've managed to find out that because cakephp doesn't automatically join hasmany relationships when you are paginating, therefore I had to add some code to tell it to join the bookings table.
Now it seems to be joining alright with the Bookings table but it's returning multiple sets of Listings. Can anyone take a look and help me out with this? Here's what I have now:
$conditions = array(
"OR" => array(
"Listing.address_one LIKE" => "%".$area."%",
"Listing.address_two LIKE" => "%".$area."%",
"Listing.city LIKE" => "%".$area."%",
"Listing.postcode LIKE" => "%".$area."%",
"Listing.title LIKE" => "%".$area."%",
)
);
$this->paginate = array(
'joins'=>array(
array(
'table'=>'bookings',
'alias'=>'Booking',
'type' =>'inner',
'conditions' =>array('Booking.checkin NOT BETWEEN ? AND ?' => array($to, $from))
)
),
'conditions'=> $conditions
);
$data = $this->paginate();

Booking is already bound to Listing in the models right? then there is no need to use bindModel on the fly.
just add your Booking.checkin BETWEEN $to AND $from into the $conditions array

Related

Paginate do not sort DESC

I can't display Agreements with agreement_number less than 7 and order it by agreement_number DESC.
I have read Pagination CakePHP Cook Book and can't find where my code is wrong. It display only less than 7, but always ASC. I have found similar question here, [that works],(CakePHP paginate and order by) and do not know why. Agreement.agreement_number is int(4).
$this->Agreement->recursive = 0;
$agreements = $this->Paginator->paginate('Agreement', array(
'Agreement.agreement_number <' => '7'
), array(
'Agreement.agreement_number' => 'desc'
)
);
$this->set('agreements', $agreements);
}
Exact cake version is 2.5.2.
... Where did you read that that was the correct syntax?
The paginate function's third parameter is for sorting (and I mean, within the table... with those down and up arrows).
List of allowed fields for ordering. This allows you to prevent
ordering on non-indexed, or undesirable columns.
You have the exact link used for documentation of the API, but you don't seem to be following it (like, from here and here)
$this->Paginator->settings = array(
'Agreement' => array(
'order' => array('Agreement.agreement_number' => 'desc')
)
);
$agreements = $this->Paginator->paginate('Agreement', array(
'Agreement.agreement_number <' => '7'));

CakePHP join with comma separated ids

Here I want to join two table with comma separated ids
For example my data is like:
[Restaurant] => Array
(
[RST_ID] => 171
[RST_NAME] => oneone
[RST_IMAGE] =>
[RST_CAT_ID] => 2,4,6
[RST_CT_ID] => 27
[RST_IS_TOP] => 3
[RST_QR_CODE] =>
[RST_CREATED_DATE] => 1394536725
[RST_MODIFIED_DATE] => 1394536725
[RST_STATUS] => 1
)
[Category] => Array
(
[CAT_ID] => 2
[CAT_NAME] => Vegetarian
[CAT_CREATED_DATE] => 1375175962
[CAT_MODIFIED_DATE] => 1375175962
[CAT_STATUS] => 1
)
My Model Code:
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'RST_CAT_ID',
'conditions' => array('Category.CAT_ID IN ( Restaurant.RST_CAT_ID)')
)
);
Real Query:
SELECT
`Restaurant`.`RST_ID`, `Restaurant`.`RST_NAME`, `Restaurant`.`RST_IMAGE`,
`Restaurant`.`RST_CAT_ID`, `Restaurant`.`RST_CT_ID`, `Restaurant`.`RST_IS_TOP`,
`Restaurant`.`RST_QR_CODE`, `Restaurant`.`RST_CREATED_DATE`,
`Restaurant`.`RST_MODIFIED_DATE`, `Restaurant`.`RST_STATUS`,
`Category`.`CAT_ID`, `Category`.`CAT_NAME`, `Category`.`CAT_CREATED_DATE`,
`Category`.`CAT_MODIFIED_DATE`, `Category`.`CAT_STATUS`, `City`.`CT_ID`,
`City`.`CT_NAME`, `City`.`CT_CREATED_DATE`, `City`.`CT_MODIFIED_DATE`,
`City`.`CT_STATUS`
FROM `dailybit_dailybites`.`restaurant` AS `Restaurant`
LEFT JOIN `dailybit_dailybites`.`category` AS `Category`
ON (`Restaurant`.`RST_CAT_ID` = `Category`.`CAT_ID`
AND `Category`.`CAT_ID` IN ( `Restaurant`.`RST_CAT_ID`))
LEFT JOIN `dailybit_dailybites`.`city` AS `City`
ON (`Restaurant`.`RST_CT_ID` = `City`.`CT_ID`)
WHERE 1 = 1
So what’s the solution here?
It's giving me just one category data that for first id only.
First have a look at this question: MySQL search in comma list
As you can see the belongsTo query is just generating a join on the single id, CakePHP by default doesn't respect this special case. You will have to alter your query and pass all the ids manually, but your DB design is bad and it doesn't follow the CakePHP conventions at all.
How do you prevent duplicates (which would waste space)
How do you remove a given value (Requires custom function, leading to possibility of errors?
How do you respond to performance issues as the size of my tables increase?
Instead of changing the query you should change this awkward DB design. You want to use HABTM here and a join table: Restaurant hasAndBelongsToMany Categoryy.
restaurants <-> restaurants_categories <-> categories
If you insist on using this bad DB design you'll have to use bindModel() and set the conditions manually:
'conditions' => array('FIND_IN_SET (Category.CAT_ID, ' . $listOfIds. ')')
I haven't tested this, try it yourself, see FIND_IN_SET() vs IN()
You'll have to have another method that gets you all the ids you want here before. Like I said, this is ineffectice and bad design.
You have to set your foreign Key false and find_in_set condition
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => false,
'conditions' => array('FIND_IN_SET(Category.CAT_ID,Restaurant.RST_CAT_ID)')
)
);
// you can pass an array at the place of 'Restaurant.RST_CAT_ID'

Include single item from related table

I have a table called items and a table called item_pics.
item_pics has an item_id, file_name and a rank field (among others).
What I'm looking for is for each item my index page's $items array to contain the file_name from the item_pics matching the item's item_id with the lowest rank. So I can access like (or something like) this in my Items/index.ctp:
foreach ($items as $item):
$img = $item['Item']['ItemPic']['file_name'];
...
I'm pretty new to CakePHP, this is my first project. I thought that this within the Item model would cause item_pics data to be pulled (although I figured all related item_pics for each item would get pulled rather than just the one with the lowest rank):
public $hasMany = array(
'ItemPic' => array(
'className' => 'ItemPic',
'foreignKey' => 'item_id',
'dependent' => false
)
}
but I can see that no item_pics data is loaded (at the bottom of items/index):
SELECT `Item`.`id`, `Item`.`title`, `Item`.`description`, `Item`.`created`, `Item`.`modified`, `Item`.`type`, `Project`.`id`, `Project`.`item_id`, `Project`.`title`, `Project`.`description`, `Project`.`rank`, `Project`.`created`, `Project`.`modified`
FROM `laurensabc`.`items` AS `Item`
LEFT JOIN `laurensabc`.`projects`
AS `Project`
ON (`Project`.`item_id` = `Item`.`id`)
WHERE `Item`.`type` IN (1, 2)
LIMIT 20
also, while I would like projects to be joined in the view pages, I don't really need them in the index page.
I've done some searching and haven't been able to find exactly what I'm looking for. I suppose I could do a query within the index view item loop, but I'm trying to make sure I do things the right way... the CakePHP way. I assume I need to change something about my model relationships but I haven't had any luck.
CakePHP - Associations - HasMany, this makes it seem like I could order by rank and limit 1. But this didn't work... and even if it did, I wouldn't want that to affect the view pages but rather just the index page.
My Controller looks like this:
public function index($type = null) {
$this->Item->recursive = 0;
$conditions = array();
if ($type == "sale") {
$conditions = array(
"Item.type" => array(self::FOR_SALE, self::FOR_SALE_OR_RENT)
);
} else if ($type == "rent" ) {
$conditions = array(
"Item.type" => array(self::FOR_RENT, self::FOR_SALE_OR_RENT)
);
} else {
$conditions = array("Item.type !=" => self::HIDDEN);
}
$paginated = $this->Paginator->paginate($conditions);
debug($paginated);
$this->set('items', $paginated);
$this->set('title', ($type == null ? "Items for Sale or Rent" : "Items for " . ucwords($type)));
}
I have also tried this on my controller, but it doesn't seem to do anything either:
$this->paginate = array(
'conditions' => $conditions,
'joins' => array(
array(
'alias' => 'ItemPic',
'table' => 'item_pics',
'type' => 'left',
'conditions' => array('ItemPic.item_id' => 'Item.id'),
'order' => array('ItemPic.rank' => 'asc'),
'limit' => 1
)
)
);
$paginated = $this->paginate($this->Item);
First, set containable behavior in AppModel (or if you don't want it on each model, put it on Item model):
public $actsAs = array('Containable');
Then, on your find query:
$items = $this->Item->find('all', array(
'contain' => array(
'ItemPic' => array(
'fields' => array('file_name'),
'order' => 'rank',
'limit' => 1
)
)
));
Then the result array you can access it like:
foreach ($items as $item):
$img = $item['ItemPic']['file_name'];
Edit: Then you should put it on the paginate query:
$this->paginate = array(
'conditions' => $conditions,
'contain' => array(
'ItemPic' => array(
'fields' => array('file_name'),
'order' => 'rank',
'limit' => 1
)
)
);
In this case, I would probably order by rank and limit 1 as you said, and make that a dynamic association just for the index page (See http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#creating-and-destroying-associations-on-the-fly). So use $this->Item->bindModel(array('hasMany' => array('ItemPic' => $options))); (which I believe should replace your current settings for HasMany ItemPic, but you may have to unbindmodel first)
Associations created through bindModel will go through for the next query only, then it'll revert to your normal settings, unless you specifically set an option to keep using the new association.
As for why it's not getting ItemPics with Items, or why trying to order by rank and limit 1 didn't work for you, I can't really say without seeing more of your code.

How to show only categories with products with CakePHP's tree behavioral?

I'm using CakePHP's tree behavior and need to know if there is any products in category or it's subcategories since I don't want to view empty categories.
I would like to do something like this:
$cat = $this->Category->find('first',array('conditions'=>array('id'=>$id)));
$test = $this->Category->find('threaded', array(
'conditions' => array(
'Category.lft >=' => $cat['Category']['lft'],
'Category.rght <=' => $cat['Category']['rght'],
'Product.InStock >'=>0 //NOT WORKING
)
));
That would be a starting point to unset not needed array dimensions. In database, categories hasMany products.
What could be best solution to this problem? Is it possible to avoid Product->find in foreach loop with category_id?
Untested
$this->Category->Behaviors->attach->('Containable');
$cat = $this->Category->find('first',array('conditions'=>array('id'=>$id)));
$test = $this->Category->find('threaded', array(
'conditions' => array(
'Category.lft >=' => $cat['Category']['lft'],
'Category.rght <=' => $cat['Category']['rght']),
// use containable behaviour and apply the condition
'contain'=>array('Product'=>array('conditions'=>
array('Product.InStock >'=> 0)
)
)
));

CakePHP paginate conditions in derived fields

i would like to paginate a list of games, where the sport is a chosen sport.
the relatition is as followed:
Game BelongsTo Competition BelongsTo Team BelongsTo sport
What i would like to do is show all games where the teams sport_id = 1.
The following doesn't work:
$this->paginate = array('limit' => 30, 'page' => 1,
'conditions' => array('Competition.Team.sport_id' => '1'),
'contain' => array('Competition', 'Competition.Team',
'Gamefield', 'Changingroom', 'ChangingroomAway', 'Gametype'),
'order'=>array('game_date'=>'asc'),
);
Can anyone help me with this one?
I've found my solution:
var $paginate=array(
'Game'=>
array(
'joins'=>array(
array('table'=>'competitions',
'alias'=>'Competition2',
'type'=>'left',
'conditions'=>array('Game.competition_id=Competition2.competition_id')
),
array('table'=>'teams',
'alias'=>'Team2',
'type'=>'left',
'conditions'=>array('Competition2.team_id=Team2.team_id')
),
),
'order'=>array('game_date'=>'asc'),
'contains'=>array('Competition2'=>array('Team2'))
));
function index() {
$datum = date('Y-m-d H:m');
$this->Game->recursive = 0;
$scope=array('OR' => array(array('Team2.sport_id' => 2), array('Team2.sport_id' =>3)), 'Game.game_date >' => $datum);
// Configure::write('debug',2);
$this->set('games', $this->paginate(null,$scope));
}
Thanks to TehThreag for helping me
Containable doesn't create any joins unless the relation would have been joined anyways, despite using Containable.
This means that for habtm, if you want a join you have to do as Michael did and specify them.
Also, doing a couple of joins should be faster with logical indexes than doing a habtm with Containable anyways, as the results from a contain for the same data as above would require an in ( id1,id2,...id# ) condition and a number of queries from there to fetch the individual related records.
The joins solution gets the data back in one db query.

Categories