Cakephp 3 pagination with 'contain' and 'condition' not working - cakephp-3.0

I have a Device Table containing different ids from different tables e.g. Location, Operators etc.
now I want only some of them shown in my view. so there is an array device_ids with the corresponding ids I want to Show.
I tried
$this->paginate['contain'] = ['Locations', 'Operators'];
and this shows me all devices with all corresponding Data.
I tried
$this->paginate['conditions'] = ['id IN' => $device_ids];
and this Shows me only the devices I want to view, but without corresponding Data.
as soon as I Combine those two in
$this->paginate[] = [
'conditions' => ['id IN' => $device_ids],
'contain' => ['Locations', 'Operators']
];
I receive a
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in IN/ALL/ANY subquery is ambiguous.
Has anyone has an Explanation for this behaviour?
What could I change to make this work.
Thanks a lot
L.

sorry,
as soon as I posted the question the answer was there...
I needed to specify the id more,
so with
$this->paginate[] = [
'conditions' => ['Devices.id IN' => $device_ids],
'contain' => ['Locations', 'Operators']];
it works fine.
Thanks for your time

You should add model name with field like ModelName.id
$this->paginate['conditions'] = ['ModelName.id IN' => $device_ids];

Related

Multiple Fields with a GroupBy Statement in Laravel

Already received a great answer at this post
Laravel Query using GroupBy with distinct traits
But how can I modify it to include more than just one field. The example uses pluck which can only grab one field.
I have tried to do something like this to add multiple fields to the view as such...
$hats = $hatData->groupBy('style')
->map(function ($item){
return ['colors' => $item->color, 'price' => $item->price,'itemNumber'=>$item->itemNumber];
});
In my initial query for "hatData" I can see the fields are all there but yet I get an error saying that 'colors', (etc.) is not available on this collection instance. I can see the collection looks different than what is obtained from pluck, so it looks like when I need more fields and cant use pluck I have to format the map differently but cant see how. Can anyone explain how I can request multiple fields as well as output them on the view rather than just one field as in the original question? Thanks!
When you use groupBy() of Laravel Illuminate\Support\Collection it gives you a deeper nested arrays/objects, so that you need to do more than one map on the result in order to unveil the real models (or arrays).
I will demo this with an example of a nested collection:
$collect = collect([
collect([
'name' => 'abc',
'age' => 1
]),collect([
'name' => 'cde',
'age' => 5
]),collect([
'name' => 'abcde',
'age' => 2
]),collect([
'name' => 'cde',
'age' => 7
]),
]);
$group = $collect->groupBy('name')->values();
$result = $group->map(function($items, $key){
// here we have uncovered the first level of the group
// $key is the group names which is the key to each group
return $items->map(function ($item){
//This second level opens EACH group (or array) in my case:
return $item['age'];
});
});
The summary is that, you need another loop map(), each() over the main grouped collection.

Join DB tables with hasMany association - store in array

In a cakePHP application I am building, a profile can have multiple locations; the tables are called "profiles" and "locations" and in the model classes I have defined a HasMany relationship. Now I want the user to be able to search profiles based on their locations. After reading some questions here and the CakePHP Cookbook, I have decided I need to use SQL joins (in reality more tables are involved, and the result of a search should be based on conditions concerning different tables).
I have written the following function inside my Profile model:
public function findProfiles($long, $lat){
$options['joins'] = array(
array('table' => 'locations',
'alias' => 'Location',
'type' => 'Inner',
'conditions' => array('Location.profile_id = Profile.id'))
);
$options['order'] = array('Location.lng ASC'); //this is just as trial
return $this->find('all',$options);
}
The code works, but I get a copy of a profile for each location it possesses. That is, if a profile possesses 5 positions, I get five instances of that profile (each instance containing all five positions!)
How can I achieve this?
[edit]
eg. let's assume I only have one profile, with two positions. I get:
result[0][Profile]
[Position][0]
[1]
[1][Profile]
[Position][0]
[1]
Where the data in result[0] and result[1] is identical.
The problem happens because of the type of join used. With inner join you'll get this return with your query
profile_id location_id
---------------------------
1 2
1 3
And cake understands that as two records of Profile, so you get repeated Profiles with the same info.
If this were all the extent of your problem, I'd say "go with Containable behaviour and forget joins", but since you said there are more tables involved, maybe the type of join can't be changed. So to get the unique Profile without repetitions, you'll have to GROUP BY the query to get
profile_id location_id
---------------------------
1 2 & 3
with a code similar to this
$options['joins'] = array(
array('table' => 'locations',
'alias' => 'Location',
'type' => 'Inner',
'conditions' => array('Location.profile_id = Profile.id')),
'group' => 'Profile.id'
);
and you'll get rid of repetitions. For future problems like this, is best to first check the actual query that gets send to the DB, check yourself if the result that the DB gives you is what you want, and if not, see what you can do in cake to change it.

CakePHP 2.3: Conditionally retrieve unique model information based on another model's use of it

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

UPDATED: Magento add customer attribute filter to order grid

I have extended the Mage_Adminhtml_Block_Sales_Order_Grid class with a custom module to add several customer attributes (Magento EE 1.10) to the grid.
Two of the attributes I added are text fields (i.e. they live in the customer_entity_varchar table, and I was able to add them to the collection and display them in the grid. So far so good.
A third attribute is a select, so the values live in the customer_entity_int, the eav_attribute_option and the eav_attribute_option_value tables. I added the necessary values to the collection (using $collection->getSelect()->joinLeft(.....). Again, so far so good.
My problem is being able to display and filter the attribute at the same time.
Inside the _prepareColumns() function in my MyCompany_MyModule_Block_Adminhtml_Order_Grid class, if I add a column like this, - as expected - I can display the values of the attribute on each row, but I don't get a drop down filter in the header:
protected function _prepareColumns()
{
...
$this->addColumn('bureau', array(
'header' => Mage::helper('sales')->__('Bureau'),
'index' => 'bureau',
'type' => 'text'
));
...
}
Following the example of status, and adding the column like this, gives me the drop down filter in the header, but it no longer displays the values for the attribute in each row:
protected function _prepareColumns()
{
...
$this->addColumn('bureau', array(
'header' => Mage::helper('sales')->__('Bureau'),
'index' => 'bureau',
'type' => 'options',
'options' => $this->_getBureauOptions(),
'filter_index' => 'value_option_table.option_id'
));
...
}
protected function _getBureauOptions()
{
$bureau = Mage::getResourceModel('eav/entity_attribute_collection')
->setCodeFilter('bureau')
->getFirstItem();
$bureauOptions = $bureau->getSource()->getAllOptions(false);
$optionsArr = array();
foreach ($bureauOptions as $option) {
$optionsArr[$option['value']] = $option['label'];
}
return $optionsArr;
}
Any advice / explanation would be much appreciated.
UPDATE:
It turns out that my code also causes a SQL error in a multi-website environment when an admin user only has permissions for some websites:
"SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'store_id' in where clause is ambiguous"
#clockworkgeek had the answer to the first part of my question.
The problem was that my joinLeft() was retrieving text values from the attribute options, while I should have been retrieving integer values when using 'type => 'options'.
Once I changed my joinLeft() to only retrieve integer values from customer_entity_int (actually a simpler join), the filtering and display worked flawlessly - thank you sir.
I will re-post my second issue (about SQL errors caused by permissions) as a separate question.

Need some help with the conditions in CakePHP!

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.