-
Notifications
You must be signed in to change notification settings - Fork 25
perf: improve mapping lookup performance #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
6c078bf
e9728c7
b4645c6
3089f3d
873f4d4
e69653d
6764217
e67e360
59df48e
0afb86c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| // Todo: do not merge this experiment | ||
|
|
||
| /** | ||
| * A task that holds a reference to a string and the source id of the mapping | ||
| * | ||
| * It makes it possible to update the referenced string at a later point | ||
| */ | ||
| class MappingTask | ||
| { | ||
| public function __construct( | ||
| public string &$reference, | ||
| public string $sourceId | ||
| ) { | ||
| } | ||
| } | ||
|
|
||
| class MappingService | ||
| { | ||
| public const PLACEHOLDER = 'placeholder-mapping-uuid'; | ||
|
|
||
| /** | ||
| * @var list<MappingTask> | ||
| */ | ||
| private array $pendingTasks = []; | ||
|
|
||
| /** | ||
| * Important: this method returns a reference to a string which is also internally stored. | ||
| * It must be called like this: | ||
| * | ||
| * $data['someId'] = &$mappingService->getMapping('sourceId'); | ||
| * | ||
| * The use of '&' is very important, otherwise it will only copy the placeholder string. | ||
| * More info on this can be found in the PHP docs: | ||
| * https://www.php.net/manual/en/language.references.return.php | ||
| */ | ||
| public function &getMapping(string $sourceId): string | ||
| { | ||
| // previously we would fetch the mapping from the DB here, | ||
| // but reaching out to the DB every time is expensive | ||
|
|
||
| // so instead we just return a placeholder string | ||
| // and update it later, when we know all mappings that are needed | ||
|
|
||
| // and then use the string reference to update the placeholders | ||
|
|
||
| $pointer = self::PLACEHOLDER; | ||
|
|
||
| $task = new MappingTask($pointer, $sourceId); | ||
| $this->pendingTasks[] = $task; | ||
|
|
||
| return $pointer; | ||
| } | ||
|
|
||
| public function resolvePendingMappings(): void | ||
| { | ||
| // in here we can fetch all requested mappings from the DB | ||
| // in a single query and update all corresponding strings | ||
| foreach ($this->pendingTasks as $task) { | ||
| $task->reference = 'changed-' . $task->sourceId; | ||
| unset($task->reference); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // a stripped-down example of a converter | ||
| function convertData(MappingService $mappingService): array | ||
| { | ||
| $data['name'] = 'John Doe'; | ||
| $data['manufacturerId'] = &$mappingService->getMapping('manufacturer01'); | ||
| $data['price'] = []; | ||
| for ($i = 0; $i < 3; ++$i) { | ||
| $data['price'][] = convertCurrency($i, $mappingService); | ||
| } | ||
|
|
||
| return $data; | ||
| } | ||
|
|
||
| // a stripped-down example of internal logic in a converter | ||
| function convertCurrency(int $i, MappingService $mappingService): array | ||
| { | ||
| $price = [ | ||
| 'id' => $i, | ||
| // unfortunately, this does not work, but there is an easy workaround below | ||
| // 'currencyId' => &$mappingService->getMapping('currency0' . $i), | ||
| ]; | ||
|
|
||
| // this works fine | ||
| $price['currencyId'] = &$mappingService->getMapping('currency0' . $i); | ||
|
|
||
| return $price; | ||
| } | ||
|
|
||
| // === Main logic below === | ||
|
|
||
| $mappingService = new MappingService(); | ||
| $convertedData = convertData($mappingService); | ||
| echo '============== Initial data after converter run ==============' . \PHP_EOL; | ||
| var_dump($convertedData); | ||
|
|
||
| echo '============== data after mapping tasks got resolved ==============' . \PHP_EOL; | ||
| $mappingService->resolvePendingMappings(); | ||
| var_dump($convertedData); | ||
|
|
||
| // === Output looks like this === | ||
| /* | ||
| ============== Initial data after converter run ============== | ||
| array(3) { | ||
| ["name"]=> | ||
| string(8) "John Doe" | ||
| ["manufacturerId"]=> | ||
| &string(24) "placeholder-mapping-uuid" | ||
| ["price"]=> | ||
| array(3) { | ||
| [0]=> | ||
| array(2) { | ||
| ["id"]=> | ||
| int(0) | ||
| ["currencyId"]=> | ||
| &string(24) "placeholder-mapping-uuid" | ||
| } | ||
| [1]=> | ||
| array(2) { | ||
| ["id"]=> | ||
| int(1) | ||
| ["currencyId"]=> | ||
| &string(24) "placeholder-mapping-uuid" | ||
| } | ||
| [2]=> | ||
| array(2) { | ||
| ["id"]=> | ||
| int(2) | ||
| ["currencyId"]=> | ||
| &string(24) "placeholder-mapping-uuid" | ||
| } | ||
| } | ||
| } | ||
| ============== data after mapping tasks got resolved ============== | ||
| array(3) { | ||
| ["name"]=> | ||
| string(8) "John Doe" | ||
| ["manufacturerId"]=> | ||
| string(22) "changed-manufacturer01" | ||
| ["price"]=> | ||
| array(3) { | ||
| [0]=> | ||
| array(2) { | ||
| ["id"]=> | ||
| int(0) | ||
| ["currencyId"]=> | ||
| string(18) "changed-currency00" | ||
| } | ||
| [1]=> | ||
| array(2) { | ||
| ["id"]=> | ||
| int(1) | ||
| ["currencyId"]=> | ||
| string(18) "changed-currency01" | ||
| } | ||
| [2]=> | ||
| array(2) { | ||
| ["id"]=> | ||
| int(2) | ||
| ["currencyId"]=> | ||
| string(18) "changed-currency02" | ||
| } | ||
| } | ||
| } | ||
| */ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| <?php declare(strict_types=1); | ||
| /* | ||
| * (c) shopware AG <info@shopware.com> | ||
| * For the full copyright and license information, please view the LICENSE | ||
| * file that was distributed with this source code. | ||
| */ | ||
|
|
||
| namespace SwagMigrationAssistant\Migration\Mapping; | ||
|
|
||
| /** | ||
| * A promise that holds a reference to a string and the data for looking up a mapping. | ||
| * It makes it possible to update the referenced string at a later point and fulfill the promise to | ||
| * replace it with an actual UUID. | ||
| * | ||
| * If you are familiar with the JS world, this is a bit similar to a JS Promise, but | ||
| * there is no automatic executor in the background and these need to be manually fulfilled. | ||
| * | ||
| * @see MappingServiceV2 | ||
| * | ||
| * @internal | ||
| */ | ||
| #[Package('fundamentals@after-sales')] | ||
|
Check failure on line 22 in src/Migration/Mapping/MappingPromise.php
|
||
| class MappingPromise | ||
| { | ||
| public function __construct( | ||
| /** | ||
| * points to the placeholder string that needs to be replaced with an actual UUID | ||
| */ | ||
| public string &$reference, | ||
| /** | ||
| * entity used for mapping lookup | ||
| */ | ||
| public string $entity, | ||
| /** | ||
| * source id used for mapping lookup | ||
| */ | ||
| public string $sourceId, | ||
| /** | ||
| * if true, the mapping will be created in DB if it does not exist | ||
| */ | ||
| public bool $shouldCreate = false, | ||
| /** | ||
| * if creating, use this Uuid to map to | ||
| */ | ||
| public ?string $createWith = null, | ||
| ) { | ||
| } | ||
|
|
||
| public function resolve(string $uuid): void | ||
| { | ||
| $this->reference = $uuid; | ||
| unset($this->reference); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not strictly necessary here, it just drops the reference and makes |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Todo: remove this and document the approach properly somewhere else
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't come very far with implementing this as an actual
MappingServiceV2and using that in ourProductConverterto validate this idea further today.But I'm still curious what others would think about this rather unusual approach 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creative! My main concern would be DX (using this right), but this is probably solvable with custom PHPStan rules or extra validation steps. Definitely worth exploring more 🙂