I have a product that has a brand. A brand_id in product table.
A brand has a collection of images.
Images have the id, product_id and path in the table.
When in the product index, you get the entity that has the associated brands data from like below. You can't go any deeper to what i want than this:
$product->brands[0]['image_id'];
To get the path, I want to be able to go something like:
$product->brand->image['path'];
Currently the product index gets its stuff like this:
$this->paginate = [
'contain' => ['Brands']
];
$products = $this->paginate($this->Products);
$this->set(compact('products'));
$this->set('_serialize', ['products']);
So how do i approach a 'subquery' or something to also get the image table data (secondary association of product, through brands)
How do I go about this?
To get deeper associations, modify your query options:
'contain' => [
'Brands' => ['Images']
]
Related
I have an existing cakephp (version 2) controller index function doing this:
$options = ['Person.name LIKE' => $term];
$this->set('people', $this->Paginator->paginate($options));
resulting in a paginated table in the view.
My Person model references a child model of Appointment, where one person has many appointments like so:
public $hasMany = [
'Appointment' => [
'className' => 'Appointment',
'foreignKey' => 'person_id',
'dependent' => false
]
]
I now need to add a Person's Oldest Appointment Date column to my table, i.e. if working with raw SQL I might do this:
select
Person.id,
Person.name,
(select
min(Appointment.Date) from Appointment
where Appointment.person_id = Person.id
) as OldestAppointmentDate
from Person
where Person.name like 'foo%'
How can I modify the paginate() parameters so that this new field is included in the results and is sortable by paginate in the usual way?
The most simple way would probably be to use a virtual field, which you can then include in the paginators fields option, something like:
// in Model/Person.php
public $virtualFields = array(
'OldestAppointmentDate' => '
select
min(Appointment.Date)
from
Appointment
where
Appointment.person_id = Person.id
';
);
// in your controller action
$this->Paginator->settings['fields'] = array(
'Person.id',
'Person.name'
'Person.OldestAppointmentDate'
);
// ...
That will include the subquery and create the required aliases accordingly, and things get stitched together automatically so that the results look like as if OldestAppointmentDate is an actual field of Person, and you can refer to it in the paginator helper like any other field, ie:
$this->Paginator->sort('Person.OldestAppointmentDate');
See also
Cookbook > Models > Virtual fields
Cookbook > Core Libraries > Components > Pagination > Query Setup
I have 3 tables
Shops - id, place_id, name...
Products - id, shop_id, name ...
Product_Tag - product_id, tag_id ... (pivot table)
Tags - id....
And I would like to get results in array like this:
array [
0 => [
"id" => 1,
"name" => "Shop name",
"products" => [...]
]
]
but I would like to search it by place_id and tag name. Something like this:
$shops = Shop::where('place_id', 1)
->with(array('products' => function($query)
{
$query->whereHas('tags', function ($query) {
$query->where('slug', 'tagname1');
});
}))->get();
This is working okay. But if none of shop products has that tag, I would still get Shop object with empty products array. Everything is okay if in that shop, at least one product has that tag. I don't want to get shop if it has empty products list. Also I think it's overhead to foreach that array and search for empty arrays and then to remove shop object. Is there better way to don't fetch from database at all ?
You can nest your 'has' statements. See: http://laravel.com/docs/5.0/eloquent#querying-relations
So this should work:
$shops = Shop::where('place_id', 1)->whereHas('products.tags', function ($query) {
$query->where('slug', 'tagname1');
})->with(products.tags)->get();
I have two rows in one of my tables which look like:
id product_id target_product_id description type
1 206587 456 sdfgdfgdfg 0
2 456 206587 fgdgfhghfgfdsgfdghfghfsd 0
When viewing the model for the row with id 1 I wish to get the second row based on where the product_id and the target_product_id are inversed. So I made a relation of:
'linked_product_relation' => array(self::HAS_ONE, 'Accessory', '',
'on'=>'linked_product_relation.target_product_id = product_id
AND link_product_relation.product_id = target_product_id')
However, it seems to only ever return null. I have checked that link_product_relation links to the table, and I get no SQL error, just a null return. If I use the relation with only link_product_relation.product_id = product_id though I do actually get a response, but only the row I am currently looking at. I seem to be missing something.
How can I get my desired output?
Edit
When I add a function to replace the relation:
function getTwinned(){
$a=Accessory::model()->findByAttributes(array('target_product_id'=>$this->product_id, 'product_id'=>$this->target_product_id));
if($a===null)
return null;
else
return $a;
}
It works perfectly.
You did not specify a foreign key ('' in your code). Try something like this:
'linked' => array(self::BELONGS_TO, 'Accessory', array(
'target_product_id'=>'product_id',
'product_id' => 'target_product_id',
)),
For more information also read the manual on this topic here and here.
In magento, I have an attribute called cl_designer, which is a select drop-down option. I want to filter the products collection on it, like this:
$collection = Mage::getModel('catalog/product')->getCollection();
$collection->addAttributeToFilter('cl_designer', array('like' => $filter));
But it doesn't work! When I print out the query with $collection->getselect(), I see that it is comparing $filter to catalog_product_entity_int.value. But this is wrong, because for select options, catalog_product_entity_int.value is the option_id, NOT the value. So how do I make it filter on the actual option value?
Assuming an example drop-down attribute named size contains the following options:
id value
22 'small'
23 'medium'
24 'large'
and you want to filter your collection by 'medium' options:
Filter by drop-down option value
To filter a product collection by option value of a product's (custom) drop-down attribute:
$sAttributeName = 'size';
$mOptionValue = 'medium';
$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->addFieldToFilter(
$sAttributeName,
array(
'eq' => Mage::getResourceModel('catalog/product')
->getAttribute($sAttributeName)
->getSource()
->getOptionId($mOptionValue)
)
);
Filter by drop-down option id
To filter a product collection by a option id of a product's (custom) drop-down attribute:
$sAttributeName = 'size';
$mOptionId = 23;
$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->addFieldToFilter(
$sAttributeName,
array('eq' => $mOptionId)
);
In short, like this:
$collection->
addAttributeToFilter(
array(
array('attribute' => 'cl_designer', 'eq' => ''),
array('attribute' => 'cl_designer', 'neq' => '')
))->
joinTable(array('cl_designer_value'=>'eav_attribute_option_value'),'option_id = cl_designer', array('cl_designer_value' => 'value'))->
addAttributeToFilter('cl_designer_value', array('like' => $filter));
The first addAttributeToFilter is needed to make it include the right catalog_product_entity_int table, and join it properly - by entity_id, attribute_id, and store_id. Next we use joinTable to connect to eav_attribute_option_value.
joinTable is complicated. The first argument is an array of tables to join, of the form alias => tablename. The tablename can be the raw name (like here), or the standard magento slash notation. The second argument is a string of the form "primary=attribute". Whatever is on the left of the = is assumed to be the column in this table that you want to use to join on, and whatever is after the = is assumed to be an attribute code. It then converts the attribute code given into a proper table.column to use in the join, BUT it does not add the table if missing - that's why we needed the first addAttributeToFilter.
The next argument to joinTable is also required, and is an array of the form alias => column, each entry of which is available for reference by its alias - so I specified array('cl_designer_value' => 'value'), which means that I can refer to cl_designer_value.value (tablealias.column) as cl_designer_value.
After the joinTable, I can now treat cl_designer_value as any other attribute code, and use it normally.
Keep in mind that joinTable joins a table by attribute code, but also that once you have joined one, the attribute code you specify in the fields array (third argument) is then available for use in your next join. So you can chain several calls to joinTable together, if you need to, although to be fair I can't really think of when you would.
At first take a look at the following model structure:
Model Building:
id
name
Model BuildingRange:
id
building_id
postalcode
Ok, so BuildingRange $belongsTo Building and Building $hasMany BuildingRange. Should be clear til' here.
Now let
$current_postalcode="12345";
I know want to do something like this in the BuildingController:
$this->paginate('Building',array('Building.BuildingRange.postalcode'=>$current_postalcode));
In text: I want to select all buildings for that an entry "BuildingRange" with $current_postalcode exists. How do you do that?
I appreciate your help!
When dealing with such a hasMany association, CakePHPs auto-magic needs two queries, one on the Building table, and one on the BuildingRange table. When passing conditions via the pagiante method, these conditions will be passed to the first query, and thus this it will fail since the associated models table isn't joined.
This problem can be solved on a few different ways, one would be using an ad-hoc join, for example:
$this->paginate = array
(
'joins' => array
(
array
(
'table' => 'building_ranges',
'alias' => 'BuildingRange',
'type' => 'LEFT',
'conditions' => array('BuildingRange.building_id = Building.id')
)
)
);
$this->paginate('Building', array('BuildingRange.postalcode' => $current_postalcode));
This would result in a query that looks something like this:
SELECT `Building`.`id`,
`Building`.`name`
FROM `buildings` AS `Building`
LEFT JOIN `building_ranges` AS `BuildingRange`
ON ( `BuildingRange`.`building_id` = `Building`.`id` )
WHERE `BuildingRange`.`postalcode` = '12345'
LIMIT 20
Note that in the conditions passed to the paginate method there is no need to reference the BuildingRange model through the Building model, ie no need to use Builduing.BuildingRange (that wouldn't work anyway).
ps, it's always good to mention the CakePHP version you are using!