Skip to content

Commit 121f4b9

Browse files
committed
011_product_modeling.md: Add a code generator example
1 parent 66cdec2 commit 121f4b9

File tree

5 files changed

+153
-16
lines changed

5 files changed

+153
-16
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ibexa_product_catalog:
2+
engines:
3+
default:
4+
type: local
5+
options:
6+
root_location_remote_id: ibexa_product_catalog_root
7+
product_type_group_identifier: 'product'
8+
variant_code_generator_strategy: 'per_product_type_code_generator'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
#
3+
4+
App\CodeGenerator\Strategy\ProductTypeCodeGeneratorDispatcher:
5+
arguments:
6+
$defaultCodeGeneratorIdentifier: 'incremental'
7+
$productTypeCodeGeneratorMap:
8+
bike: 'bike'
9+
tags:
10+
- { name: 'ibexa.product_catalog.code_generator', type: 'per_product_type_code_generator' }
11+
12+
App\CodeGenerator\Strategy\BikeCodeGenerator:
13+
tags:
14+
- { name: 'ibexa.product_catalog.code_generator', type: 'bike' }
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\CodeGenerator\Strategy;
6+
7+
use Ibexa\Contracts\Core\Exception\InvalidArgumentException;
8+
use Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException;
9+
use Ibexa\Contracts\ProductCatalog\Local\CodeGenerator\CodeGeneratorContext;
10+
use Ibexa\Contracts\ProductCatalog\Local\CodeGenerator\CodeGeneratorInterface;
11+
12+
final class BikeCodeGenerator implements CodeGeneratorInterface
13+
{
14+
public function generateCode(CodeGeneratorContext $context): string
15+
{
16+
if (!$context->hasBaseProduct()) {
17+
throw new InvalidArgumentException('$context', 'missing base product');
18+
}
19+
20+
$frameSize = $context->getAttributes()['frame_size'];
21+
$wheelSize = $context->getAttributes()['wheel_diameter'];
22+
$frameShape = $context->getAttributes()['frame_shape'][0];
23+
$gearBundle = $context->getAttributes()['gear_bundle'];
24+
25+
return $context->getBaseProduct()->getCode() . "--$frameSize-$wheelSize-$frameShape-$gearBundle";
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\CodeGenerator\Strategy;
6+
7+
use Ibexa\Contracts\Core\Exception\InvalidArgumentException;
8+
use Ibexa\Contracts\ProductCatalog\Local\CodeGenerator\CodeGeneratorContext;
9+
use Ibexa\Contracts\ProductCatalog\Local\CodeGenerator\CodeGeneratorInterface;
10+
use Ibexa\ProductCatalog\Local\Repository\CodeGenerator\CodeGeneratorRegistryInterface;
11+
12+
final class ProductTypeCodeGeneratorDispatcher implements CodeGeneratorInterface
13+
{
14+
private CodeGeneratorRegistryInterface $codeGeneratorRegistry;
15+
private string $defaultCodeGeneratorIdentifier;
16+
private array $productTypeCodeGeneratorMap;
17+
18+
public function __construct(CodeGeneratorRegistryInterface $codeGeneratorRegistry, string $defaultCodeGeneratorIdentifier='incremental', array $productTypeCodeGeneratorMap=[]) {
19+
$this->codeGeneratorRegistry = $codeGeneratorRegistry;
20+
$this->defaultCodeGeneratorIdentifier = $defaultCodeGeneratorIdentifier;
21+
$this->productTypeCodeGeneratorMap = $productTypeCodeGeneratorMap;
22+
}
23+
24+
public function generateCode(CodeGeneratorContext $context): string
25+
{
26+
if (!$context->hasBaseProduct()) {
27+
throw new InvalidArgumentException('$context', 'missing base product');
28+
}
29+
30+
$productTypeIdentifier = $context->getBaseProduct()->getProductType()->getIdentifier();
31+
$codeGeneratorIdentifier = array_key_exists($productTypeIdentifier, $this->productTypeCodeGeneratorMap) ? $this->productTypeCodeGeneratorMap[$productTypeIdentifier] : $this->defaultCodeGeneratorIdentifier;
32+
33+
if ($this->codeGeneratorRegistry->hasCodeGenerator($codeGeneratorIdentifier)) {
34+
return $this->codeGeneratorRegistry->getCodeGenerator($codeGeneratorIdentifier)->generateCode($context);
35+
} else {
36+
throw new InvalidArgumentException('$productTypeCodeGeneratorMap', "no code generator '$codeGeneratorIdentifier' registered");
37+
}
38+
}
39+
}

docs/trainings/commerce/pim/011_product_modeling.md

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -159,35 +159,84 @@ TODO: To have one product type per base product can happen.
159159
The exercise consists into modeling the following catalog of mountain bikes (MTB).
160160
The catalog is split in series, each series got few base products with variations.
161161

162+
- Mountain Bike
163+
- 4 Series
164+
- Fuji
165+
- Matterhorn
166+
- Annapurna
167+
- Etna
168+
- 5 Series
169+
- Kilimanjaro
170+
- Stádda
171+
- Aconcagua
172+
- Ventoux
173+
- Castor
174+
162175
To simplify casual customer experience (and above all the exercise), the vendor don't give a lot of choices.
163176

164177
- Manufacturers, brands and models are predefined.
165178
- Front and rear gears, transmission and shifting system are set in one bundle with predefined models.
166179

167180
The following table shows only properties that can vary. When not all combinations are available, the product has multiple lines.
168181

169-
| Name | Code | Material | Frame shapes | Frameset + wheel sizes | Saddle | Paint job | Gears | Price |
170-
|:------------|:-------------|:----------|:---------------------------|:-----------------------|:-----------------|:-----------------------|:-------|------:|
171-
| Fuji | MTB-S4-4-1-* | Aluminium | Diamond | [S, M, L, XL] + 29″ | Thin | [Sakura, Ronin] | B-2x10 | 3776€ |
172-
| Fuji | MTB-S4-4-2-* | Aluminium | [Diamond, Step-through] | [S, M, L] + 29″ | [Thin, Large] | [Sakura, Ronin] | B-1x10 | 3676€ |
173-
| Fuji | MTB-S4-4-3-* | Aluminium | [Diamond, Step-through] | XS + 27.5″ | [Thin, Large] | [Sakura, Ronin] | B-1x08 | 3666€ |
174-
| Matterhorn | MTB-S4-5-* | Aluminium | Diamond | [S, M, L, XL] + 29″ | [Thin, Large] | [Snow, Rock] | B-2x12 | 4478€ |
175-
| Annapurna | MTB-S4-6-* | Carbon | Diamond w/ suspension | [S, M, L, XL] + 29″ | [Thin, Noseless] | Annapurna | A-3x12 | 8091€ |
176-
| Etna | MTB-S4-7-1-* | Aluminium | [Diamond, Step-through] | [S, M, L, XL] + 29″ | [Thin, Large] | Etna | B-1x06 | 3369€ |
177-
| Etna | MTB-S4-7-2-* | Aluminium | [Diamond, Step-through] | XS + 27.5″ | [Thin, Large] | Etna | B-1x06 | 3339€ |
178-
| Kilimanjaro | MTB-S5-0-* | Aluminium | Step-through w/ suspension | [S, M, L, XL] + 29″ | [Thin, Large] | [Shira, Mawenzi, Kibo] | A-2x12 | 5895€ |
179-
| Stádda | MTB-S5-1-* | Aluminium | Step-through | XS + [26″, 27.5″] | Large | [Sunrise, Sunset] | C-1x03 | 1392€ |
180-
| Aconcagua | MTB-S5-2-* | Carbon | Diamond w/ suspension | [S, M, L, XL] + 29″ | [Thin, Noseless] | [Condor, Llama] | A-3x12 | 6960€ |
181-
| Ventoux | MTB-S5-3-* | Aluminium | Step-through | XS + [26″, 27.5″] | [Thin, Large] | [Provence, Mistral] | C-1x04 | 1910€ |
182-
| Castor | MTB-S5-4-* | Aluminium | Diamond | [S, M, L, XL] + 29″ | [Thin, Large] | [Castor, Pollux] | B-2x12 | 4225€ |
182+
| Name | Base code | Material | Frame shapes | Frameset + wheel sizes | Saddle | Paint job | Gears | Price |
183+
|:------------|:----------|:----------|:---------------------------|:-----------------------|:-----------------|:-----------------------|:--------:|------:|
184+
| Fuji | MTB-S4-4 | Aluminium | Diamond | [S, M, L, XL] + 29″ | Thin | [Sakura, Ronin] | G02-2x10 | 3776€ |
185+
| Fuji | MTB-S4-4 | Aluminium | [Diamond, Step-through] | [S, M, L] + 29″ | [Thin, Large] | [Sakura, Ronin] | G02-1x10 | 3676€ |
186+
| Fuji | MTB-S4-4 | Aluminium | [Diamond, Step-through] | XS + 27.5″ | [Thin, Large] | [Sakura, Ronin] | G02-1x08 | 3666€ |
187+
| Matterhorn | MTB-S4-5 | Aluminium | Diamond | [S, M, L, XL] + 29″ | [Thin, Large] | [Snow, Rock] | G02-2x12 | 4478€ |
188+
| Annapurna | MTB-S4-6 | Carbon | Diamond w/ suspension | [S, M, L, XL] + 29″ | [Thin, Noseless] | Annapurna | G01-3x12 | 8091€ |
189+
| Etna | MTB-S4-7 | Aluminium | [Diamond, Step-through] | [S, M, L, XL] + 29″ | [Thin, Large] | Etna | G02-1x06 | 3369€ |
190+
| Etna | MTB-S4-7 | Aluminium | [Diamond, Step-through] | XS + 27.5″ | [Thin, Large] | Etna | G02-1x06 | 3339€ |
191+
| Kilimanjaro | MTB-S5-0 | Aluminium | Step-through w/ suspension | [S, M, L, XL] + 29″ | [Thin, Large] | [Shira, Mawenzi, Kibo] | G03-2x12 | 5895€ |
192+
| Stádda | MTB-S5-1 | Aluminium | Step-through | XS + [26″, 27.5″] | Large | [Sunrise, Sunset] | G04-1x03 | 1392€ |
193+
| Aconcagua | MTB-S5-2 | Carbon | Diamond w/ suspension | [S, M, L, XL] + 29″ | [Thin, Noseless] | [Condor, Llama] | G01-3x12 | 6960€ |
194+
| Ventoux | MTB-S5-3 | Aluminium | Step-through | XS + [26″, 27.5″] | [Thin, Large] | [Provence, Mistral] | G04-1x04 | 1910€ |
195+
| Castor | MTB-S5-4 | Aluminium | Diamond | [S, M, L, XL] + 29″ | [Thin, Large] | [Castor, Pollux] | G03-2x12 | 4225€ |
183196

184197
- Create the attribute group(s)
185198
- Create the attributes
186199
- Create the product type(s)
187200
- Create the products
188-
- Create the variants
201+
- Create the product variants
202+
203+
The product variants codes can be generated automatically from the base product code
204+
by the default 'incremental' code generator as product variants codes won't be used in the training.
205+
But, if you're curious, you can read or implements as a bonus the following custom code generators.
206+
207+
??? note "Bonus: Code generator"
208+
209+
A code generator is associated to a whole catalog/repository.
210+
211+
See how to [create custom product code generator strategy](create_product_code_generator.md).
212+
213+
To be able to generate product variants' codes from their attributes, a filtering/dispatching code generator is needed.
214+
215+
In `config/services.yaml`, the code generator services declaration looks like this:
216+
```yaml
217+
[[= include_file('code_samples/trainings/commerce/pim/001_product_modeling/config/services.yaml', 0, None, ' ') =]]
218+
```
219+
The `ProductTypeCodeGeneratorDispatcher` is a code generator using other code generator depending on the product type. Its service receive a map associating product type itendifier to code generator type identifier.
220+
It also have a default code generator for product type not in the map.
221+
222+
```php
223+
[[= include_file('code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/ProductTypeCodeGeneratorDispatcher.php', 0, None, ' ') =]]
224+
```
225+
226+
The `BikeCodeGenerator` is a code generator dedicated to the `bike` product type. It must cover every `bike` attributes that can vary to ensure code unicity.
227+
228+
```php
229+
[[= include_file('code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/BikeCodeGenerator.php', 0, None, ' ') =]]
230+
```
231+
232+
In `config/packages/ibexa_product_catalog.yaml` is set the use of `per_product_type_code_generator` for the `default` engine/repository:
233+
```yaml hl_lines="8"
234+
[[= include_file('code_samples/trainings/commerce/pim/001_product_modeling/config/packages/ibexa_product_catalog.yaml', 0, None, ' ') =]]
235+
```
236+
237+
??? note "TODO: Possible solution(s)"
189238

190-
TODO: [Create custom product code generator strategy](create_product_code_generator.md)
239+
TODO: Propose grouped attributes and product type(s), illustrate their usage with few products and product variants.
191240

192241
Your new products are in the "Uncategorized products" section of the **Products** admin page.
193242
It's now time to fix this in the next chapter.

0 commit comments

Comments
 (0)