Get list of objects only when hasMany relations has element - php

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();

Related

How can I build query by the lowest hierarchy level?

On the frontend there is a table that shows information about users and their position in the company.
There are 4 dropdowns for each level of company hierarchy:
groups, units, departments, teams.
These dropdowns are meant to filter out users to display in the table from the selected hierarchy.
Each dropdown has its unique identifier like group1, unit4, department10, team99.
Each hierarchy holds data about its ancestors. For example, team99 belongs to department10, which belongs to unit4 which belongs to group1.
Then if team99 is selected, it will be an array that contains all of its ancestors: unit4, department10 and group1.
From each of these selections I need to build a select query:
foreach ($selections as $selection) {
$fullQuery->orWhere(function($query) {
$query->where("group", "=", $selection->group) //"group1"
->where("unit", "=", $selection->unit) //"unit4"
->where("department", "=", $selection->department) //"department10"
->where("team", "=", $selection->team) //"team99"
})
}
The dropdowns in the frontend are free selections so a user can first choose the team and then choose group.
In this case the returned $selections would be an array containing group1 alone, and another array containing group1, unit4, deprtment10 and team99.
If I don't filter the results beforehand, the query builder would be both for where group = group1 or where all the others (group1, unit4, department10, team99).
But this is not what I want, I need to build the query with the lowest of selections.
How can I prepare the data such that it will ignore the unnecessary hierarchies?
Another example:
received input:
[
["group2"],
["group2", "unit11", "department50", "team10"],
["group2", "unit11", "department50", "team58"],
["group2", "unit10"],
["group5", "unit23"],
["group5", "unit23", "department101"]
]
Then I will need to build a query from the lowest hierarchies, in this case:
["group2", "unit11", "department50", "team10"],
["group2", "unit11", "department50", "team58"],
["group2", "unit10"],
["group5", "unit23", "department101"]
If you need to essentially remove all arrays that are subsets of some other array you can do:
$filteredSelections = collect($selections);
$filteredSelections = $filteredSelections->filter(
fn ($selection) => $filteredSelections->first(
fn ($item) => $selection !== $item && array_intersect($selection, $item) == $selection
) == null;
);

Cakephp get childrens associated data

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']
]

php: Assigning Array Keys when creating multilevel array

I inherited the project and the data structure is a bit messy. Basically, the users input data and it's non-hierarchically structured ("pages" is not a property of "book title" but both are just values without any relation).
Now I want to create a more ordered structure, i.e. I want to list the books by author and the book details by title.
foreach($items as $item){
$books[$item->author][$item->title] = array(
"pages" => $item->details->pages->value,
"published" => $item->details->year->value,
"genres" => $item->details->genres->value
);
}
I would like to assign keys to the first two array levels. I tried $books["author" => $item->author]["title" => $item->title], but got an error message.
How can I assign keys in such a situation without having to run the whole thing through multiple foreach loops?
EDIT: a note on the object structure:
The site runs on a CMS and the client built his own input form with a plugin.
All data is saved in blogposts. These blogposts have default input fields ($item->author and $item->title) and then there is a custom field option where the client input all the details, i.e. $item->details->pages->value (value as in input value of that field).
The desired structure would be something like this:
books
author
title
pages
year
genres (array)
title
pages
year
genres (array)
title
pages
year
genres (array)
author
title
pages
year
genres (array)
title
pages
year
genres (array)
title
pages
year
genres (array)
This would allow me to loop through the whole $books array, create a for each author and create a table that lists one $title with details per row.
Try this,
$books=array();
foreach($items as $item){
$books[]=array(
'author'=>$item->author,
'title'=>$item->title,
'details'=>array(
"pages" => $item->details->pages->value,
"published" => $item->details->year->value,
"genres" => $item->details->genres->value
)
);
}
Updated code, you just need [] after [$item->author][$item->title] so that it can be multidimensional array of your desired author and title,
$books=array();
foreach($items as $item){
$books[$item->author][$item->title][] =array(
// ---------------------------^^, this will make it nested array
"pages" => $item->details->pages->value,
"published" => $item->details->year->value,
"genres" => $item->details->genres->value
);
}
And access the array like,
$str='';
foreach($books as $author=>$titleDetails){
$str.='<h1>'.$author.'</h1>';
foreach($titleDetails as $title=>$details){
$str.='<br/><h2>'.$title.'</h2>';
$str.='<br/><h3>'.$details['pages'].'</h3>';//.. and so on
}
}
echo $str;
#media (min-width:481px){
#yiv1734505183 .yiv1734505183mobile-hide{
display:block;overflow:visible;width:auto
!important;max-height:inherit !important;min-height:auto
!important;}
}#media (min-width:481px

How to KeyBy where multiple items have the same key

I am using Laravel Collections methods and am trying to key my query results (which are a collection) by the id. The problem is I have multiple entries with the same id, but point to different countries and I want to have all of the values, not just the last one.
Here is my code that i am using so far:
$allCountries = new Collection($allCountries);
$offerCountries = $allCountries->keyBy('id');
dd($offerCountries);
foreach ($offer as $o) {
$o->countries = $allCountries->get($o->id);
}
To explain, my query puts the results in $allCountries which contains ids and countries and those results looks something like this
id=>225, country=>US
id=>225, country=>IT
id=>3304, country=>NZ
Just to give you a quick idea. I want to key this by the id which results in $offerCountries. I then loop thru a previous Collection that contains offers which have a certain ID that relates to the country result by id. So for the offer 225, the countries it contains are US and IT. I loop thru each offer and set the countries object equal to all the $allCountries id that it equals. The problem I have here is keyBy overwrites the value and only takes the last one. I am hoping to get some results like this:
[
225 => countries: {'id' => 225, 'country' => 'US'}, {'id' =>
'225', 'country' => 'IT'}
3304 => ['id' => 3304, 'country' => 'NZ'],
]
Is there a laravel method to do this, or do I need to write my own keyBy so it does not overwrite. If so, how can I get started to write this method?
Thanks
Instead of using keyBy, use groupBy:
$countriesById = collect($allCountries)->groupBy('id');
You could use filter and create a custom filter
$filtered = $allCountries->filter(function ($item) use ($id) {
return $item->id == $id;
});
$filtered->all();

How do I filter a magento collection by a select drop-down attribute?

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.

Categories