ver. 0.302: REST API product variants, attributes dictionary, attribute filtering
- Add variant CRUD endpoints (variants, create_variant, update_variant, delete_variant) - Add dictionaries/attributes endpoint with multilingual names and values - Add attribute_* filter params for product list filtering by attribute values - Enrich product detail attributes with translated names (attribute_names, value_names) - Include variants array in product detail response for parent products - Add price_brutto validation on product create - Batch-load attribute/value translations (4 queries instead of N+1) - Add 43 new unit tests (730 total, 2066 assertions) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -534,6 +534,127 @@ class AttributeRepository
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca aktywne atrybuty z wartościami i wielojęzycznymi nazwami dla REST API.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function listForApi(): array
|
||||
{
|
||||
// 1. Get all active attribute IDs (1 query)
|
||||
$rows = $this->db->select('pp_shop_attributes', ['id', 'type', 'status'], [
|
||||
'status' => 1,
|
||||
'ORDER' => ['o' => 'ASC'],
|
||||
]);
|
||||
if (!is_array($rows) || empty($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attrIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$id = (int)($row['id'] ?? 0);
|
||||
if ($id > 0) {
|
||||
$attrIds[] = $id;
|
||||
}
|
||||
}
|
||||
if (empty($attrIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 2. Batch load ALL attribute translations (1 query)
|
||||
$allAttrTranslations = $this->db->select(
|
||||
'pp_shop_attributes_langs',
|
||||
['attribute_id', 'lang_id', 'name'],
|
||||
['attribute_id' => $attrIds]
|
||||
);
|
||||
$attrNamesMap = [];
|
||||
if (is_array($allAttrTranslations)) {
|
||||
foreach ($allAttrTranslations as $t) {
|
||||
$aId = (int)($t['attribute_id'] ?? 0);
|
||||
$langId = (string)($t['lang_id'] ?? '');
|
||||
if ($aId > 0 && $langId !== '') {
|
||||
$attrNamesMap[$aId][$langId] = (string)($t['name'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Batch load ALL values for those attribute IDs (1 query)
|
||||
$allValueRows = $this->db->select(
|
||||
'pp_shop_attributes_values',
|
||||
['id', 'attribute_id', 'is_default', 'impact_on_the_price'],
|
||||
[
|
||||
'attribute_id' => $attrIds,
|
||||
'ORDER' => ['id' => 'ASC'],
|
||||
]
|
||||
);
|
||||
$valuesByAttr = [];
|
||||
$allValueIds = [];
|
||||
if (is_array($allValueRows)) {
|
||||
foreach ($allValueRows as $vRow) {
|
||||
$valueId = (int)($vRow['id'] ?? 0);
|
||||
$attrId = (int)($vRow['attribute_id'] ?? 0);
|
||||
if ($valueId > 0 && $attrId > 0) {
|
||||
$valuesByAttr[$attrId][] = $vRow;
|
||||
$allValueIds[] = $valueId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Batch load ALL value translations (1 query)
|
||||
$valueNamesMap = [];
|
||||
if (!empty($allValueIds)) {
|
||||
$allValueTranslations = $this->db->select(
|
||||
'pp_shop_attributes_values_langs',
|
||||
['value_id', 'lang_id', 'name'],
|
||||
['value_id' => $allValueIds]
|
||||
);
|
||||
if (is_array($allValueTranslations)) {
|
||||
foreach ($allValueTranslations as $vt) {
|
||||
$vId = (int)($vt['value_id'] ?? 0);
|
||||
$langId = (string)($vt['lang_id'] ?? '');
|
||||
if ($vId > 0 && $langId !== '') {
|
||||
$valueNamesMap[$vId][$langId] = (string)($vt['name'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Assemble result in-memory
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$attributeId = (int)($row['id'] ?? 0);
|
||||
if ($attributeId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$names = isset($attrNamesMap[$attributeId]) ? $attrNamesMap[$attributeId] : [];
|
||||
|
||||
$values = [];
|
||||
if (isset($valuesByAttr[$attributeId])) {
|
||||
foreach ($valuesByAttr[$attributeId] as $vRow) {
|
||||
$valueId = (int)$vRow['id'];
|
||||
$impact = $vRow['impact_on_the_price'];
|
||||
$values[] = [
|
||||
'id' => $valueId,
|
||||
'names' => isset($valueNamesMap[$valueId]) ? $valueNamesMap[$valueId] : [],
|
||||
'is_default' => (int)($vRow['is_default'] ?? 0),
|
||||
'impact_on_the_price' => ($impact !== null && $impact !== '') ? (float)$impact : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'id' => $attributeId,
|
||||
'type' => (int)($row['type'] ?? 0),
|
||||
'status' => (int)($row['status'] ?? 0),
|
||||
'names' => $names,
|
||||
'values' => $values,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{sql: string, params: array<string, mixed>}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user