Skip to content

Commit e55a811

Browse files
committed
Publish product data preloader module
0 parents  commit e55a811

24 files changed

+1133
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
.idea

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 EcomDev B.V.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Product Data Pre-Loader
2+
3+
Magento platform code and third-party extensions in a lot of cases produce redundant database queries on product collections load.
4+
5+
This module provides an easy way to pre-load data for product collections like prices, stock data, and many more by using different types of load types.
6+
Right now it supports 3 types of loaded product collections:
7+
8+
- `list` Products that are using price index in selection, so it is safe to use stale index data for data loader
9+
- `cart` Products that are used to calculate customers shopping cart and require accurate data to be loaded from database
10+
- `other` Products that are not falling into any of the above categories
11+
12+
In order to pre-load data for a product collection, you need to implement `EcomDev\ProductDataPreLoader\DataService\DataLoader` interface with such methods:
13+
- `isApplicable(string $type): bool` method that is used to decide if your loader compatible with specific product collection type.
14+
- `load(ScopeFilter $filter, ProductAdapter[] $products): array` method that preloads data into `LoadService` that can be used later to access data by your loader id.
15+
16+
17+
Custom loaders should be added to `LoadService` object via DI configuration like this:
18+
19+
```xml
20+
<type name="EcomDev\ProductDataPreLoader\DataService\LoadService">
21+
<arguments>
22+
<argument name="loaders" xsi:type="array">
23+
<item name="my_custom_loader" xsi:type="object">My\Module\Loader\MyCustomLoader</item>
24+
</argument>
25+
</arguments>
26+
</type>
27+
```

composer.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "ecomdev/magento2-product-data-preloader",
3+
"version": "1.0.0",
4+
"description": "Magento 2 Product Data Pre-Loader FTW",
5+
"type": "magento2-module",
6+
"require": {
7+
"magento/framework": "*",
8+
"magento/module-catalog": "~103.0|~104.0",
9+
"magento/module-quote": "~101.0",
10+
"magento/module-customer": "~102.0|~103.0",
11+
"php": "~7.2"
12+
},
13+
"license": [
14+
"MIT"
15+
],
16+
"repositories": {
17+
"magento": {
18+
"type": "composer",
19+
"url": "https://repo.magento.com/"
20+
}
21+
},
22+
"autoload": {
23+
"files": [
24+
"src/registration.php"
25+
],
26+
"psr-4": {
27+
"EcomDev\\ProductDataPreLoader\\": "src"
28+
}
29+
}
30+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
/**
3+
* Copyright © EcomDev B.V. All rights reserved.
4+
*/
5+
declare(strict_types=1);
6+
7+
namespace EcomDev\ProductDataPreLoader\DataService;
8+
9+
use Magento\Framework\Exception\LocalizedException;
10+
use Magento\Framework\Exception\NoSuchEntityException;
11+
use Magento\Framework\Session\SessionStartChecker;
12+
use Magento\Customer\Model\Session;
13+
14+
/**
15+
* Customer group provider for dynamic resolution of it
16+
*
17+
*/
18+
class CustomerGroupProvider
19+
{
20+
/**
21+
* Customer session instance
22+
*
23+
* @var Session\Proxy
24+
*/
25+
private $customerSession;
26+
27+
/**
28+
* Session availability checker
29+
*
30+
* @var SessionStartChecker
31+
*/
32+
private $sessionChecker;
33+
34+
/**
35+
* CustomerGroupProvider constructor.
36+
*
37+
* @param Session\Proxy $customerSession
38+
* @param SessionStartChecker $sessionChecker
39+
*/
40+
public function __construct(Session\Proxy $customerSession, SessionStartChecker $sessionChecker)
41+
{
42+
$this->customerSession = $customerSession;
43+
$this->sessionChecker = $sessionChecker;
44+
}
45+
46+
/**
47+
* Return current customer group
48+
*
49+
* @return int
50+
*/
51+
public function getCustomerGroupId(): int
52+
{
53+
if ($this->sessionChecker->check()) {
54+
try {
55+
return $this->customerSession->getCustomerGroupId();
56+
}
57+
catch (NoSuchEntityException | LocalizedException $e) {
58+
return 0;
59+
}
60+
}
61+
62+
return 0;
63+
}
64+
}

src/DataService/DataLoader.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
/**
3+
* Copyright © EcomDev B.V. All rights reserved.
4+
*/
5+
6+
declare(strict_types=1);
7+
8+
namespace EcomDev\ProductDataPreLoader\DataService;
9+
10+
/**
11+
* Interface for multiple hooks that are going to be used to preload data
12+
* for product collection
13+
*/
14+
interface DataLoader
15+
{
16+
const TYPE_CART = 'cart';
17+
const TYPE_LIST = 'list';
18+
const TYPE_OTHER = 'other';
19+
20+
/**
21+
* Preloads data for a product by using product adapter
22+
*
23+
* Result should be keyed by ID of the supplied products.
24+
*
25+
* @param ScopeFilter $filter
26+
* @param ProductWrapper[] $products
27+
* @return array
28+
*/
29+
public function load(ScopeFilter $filter, array $products): array;
30+
31+
/**
32+
* Checks if the preloader is applicable for this type of data.
33+
*
34+
* @param string $type
35+
* @return bool
36+
*/
37+
public function isApplicable(string $type): bool;
38+
}

src/DataService/LoadService.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
/**
3+
* Copyright © EcomDev B.V. All rights reserved.
4+
*/
5+
declare(strict_types=1);
6+
7+
8+
namespace EcomDev\ProductDataPreLoader\DataService;
9+
10+
/**
11+
* Load service
12+
*/
13+
class LoadService
14+
{
15+
/**
16+
* @var array
17+
*/
18+
private $storage = [];
19+
20+
/**
21+
* @var array
22+
*/
23+
private $skuToId = [];
24+
25+
/**
26+
* @var DataLoader[]
27+
*/
28+
private $loaders;
29+
30+
/**
31+
* PreloadStorage constructor.
32+
*
33+
* @param DataLoader[] $loaders
34+
*/
35+
public function __construct(array $loaders)
36+
{
37+
$this->loaders = array_filter($loaders, function ($loader) {
38+
return $loader instanceof DataLoader;
39+
});
40+
}
41+
42+
/**
43+
* Checks if SKU is assigned
44+
*
45+
* @param string $sku
46+
* @return int|null
47+
*/
48+
public function skuToId(string $sku): ?int
49+
{
50+
return $this->skuToId[$sku] ?? null;
51+
}
52+
53+
/**
54+
* Check if there is data available for specified product
55+
* and type of it
56+
*
57+
* @param int $productId
58+
* @param string $type
59+
* @return bool
60+
*/
61+
public function has(int $productId, string $type): bool
62+
{
63+
return isset($this->storage[$type][$productId]);
64+
}
65+
66+
/**
67+
* Retrieves data for a product that was pre-loaded
68+
*
69+
* @param int $productId
70+
* @param string $type
71+
* @return array
72+
*/
73+
public function get(int $productId, string $type): array
74+
{
75+
return $this->storage[$type][$productId] ?? [];
76+
}
77+
78+
/**
79+
* Preload data types
80+
*
81+
* @param string $type
82+
* @param ScopeFilter $filter
83+
* @param ProductWrapper[] $products
84+
*/
85+
public function load(string $type, ScopeFilter $filter, array $products)
86+
{
87+
foreach ($this->loaders as $code => $loader) {
88+
if (!$loader->isApplicable($type)) {
89+
continue;
90+
}
91+
92+
if (!isset($this->storage[$code])) {
93+
$this->storage[$code] = [];
94+
}
95+
96+
$productsToLoad = array_diff_key($products, $this->storage[$code]);
97+
if (!$productsToLoad) {
98+
continue;
99+
}
100+
101+
$defaultData = array_fill_keys(array_keys($productsToLoad), []);
102+
103+
foreach ($productsToLoad as $productId => $adapter) {
104+
$this->skuToId[$adapter->getSku()] = $productId;
105+
}
106+
107+
$this->storage[$code] += $defaultData + $loader->load($filter, $productsToLoad);
108+
}
109+
}
110+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
/**
3+
* Copyright © EcomDev B.V. All rights reserved.
4+
*/
5+
declare(strict_types=1);
6+
7+
namespace EcomDev\ProductDataPreLoader\DataService;
8+
9+
use Magento\Catalog\Model\Product;
10+
11+
/**
12+
* Product wrapper
13+
*/
14+
class MagentoProductWrapper implements ProductWrapper
15+
{
16+
/**
17+
* @var Product
18+
*/
19+
private $product;
20+
21+
/**
22+
* MagentoProductWrapper constructor.
23+
*
24+
* @param Product $product
25+
*/
26+
public function __construct(Product $product)
27+
{
28+
$this->product = $product;
29+
}
30+
31+
/**
32+
* Returns product SKU
33+
*
34+
* @return string
35+
*/
36+
public function getSku(): string
37+
{
38+
return $this->product->getSku();
39+
}
40+
41+
/**
42+
* Returns product ID
43+
*
44+
* @return int
45+
*/
46+
public function getId(): int
47+
{
48+
return (int)$this->product->getId();
49+
}
50+
51+
/**
52+
* Check if product is of a type
53+
*
54+
* @param string ...$type
55+
* @return bool
56+
*/
57+
public function isType(string ...$type): bool
58+
{
59+
return in_array($this->product->getTypeId(), $type, true);
60+
}
61+
62+
/**
63+
* Updates field in wrapped product
64+
*
65+
* @param string $fieldName
66+
* @param $value
67+
*/
68+
public function updateField(string $fieldName, $value): void
69+
{
70+
$this->product->setData($fieldName, $value);
71+
}
72+
}

0 commit comments

Comments
 (0)