Complete Domain-Driven Architecture migration: - Phase 1-4: Transport, ProductSet, Coupon, Shop, Search, Basket, ProductCustomField, Category, ProductAttribute, Promotion - Phase 5: Order (~562 lines) + Product (~952 lines) - ~20 Product methods migrated to ProductRepository - Apilo sync migrated to OrderAdminService - Production hotfixes: stale Redis cache (prices 0.00), unqualified Product:: refs in LayoutEngine, object->array template conversion - AttributeRepository::getAttributeValueById() Redis cache added Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
235 lines
12 KiB
Markdown
235 lines
12 KiB
Markdown
# Plan refaktoryzacji `autoload/shop/` — usunięcie legacy klas
|
||
|
||
## Kontekst
|
||
|
||
Katalog `autoload/shop/` zawierał **12 legacy klas** (~2 363 linii kodu), które pełniły rolę fasad/wrapperów nad warstwą `Domain\`. Wszystkie klasy zostały usunięte — logika przeniesiona do warstwy `Domain\` / `Shared\`, wywołania zaktualizowane.
|
||
|
||
**Status: UKOŃCZONE** (wszystkie 12 klas usunięte, katalog `autoload/shop/` pusty)
|
||
|
||
## Status
|
||
|
||
| Faza | Klasy | Status |
|
||
|------|-------|--------|
|
||
| 1 | Transport, ProductSet, Coupon | ✅ Ukończona |
|
||
| 2 | Shop, Search, Basket | ✅ Ukończona |
|
||
| 3 | ProductCustomField, Category, ProductAttribute | ✅ Ukończona |
|
||
| 4 | Promotion | ✅ Ukończona |
|
||
| 5 | Order, Product | ✅ Ukończona |
|
||
|
||
---
|
||
|
||
## Faza 1 — Trywialne fasady (0 logiki do migracji)
|
||
|
||
### 1.1 `class.Transport.php` ✅ (~31 linii)
|
||
**Jedyne zewnętrzne użycie:**
|
||
- `admin\Controllers\ShopOrderController.php` → `\shop\Transport::transport_list()`
|
||
|
||
**Plan:**
|
||
- Dodać metodę `allActiveOrdered()` do `Domain\Transport\TransportRepository` (odpowiednik `transport_list()`)
|
||
- Zamienić wywołanie w `ShopOrderController`
|
||
- Usunąć `class.Transport.php`
|
||
|
||
### 1.2 `class.ProductSet.php` ✅ (~49 linii)
|
||
**Jedyne zewnętrzne użycie:**
|
||
- `admin\Controllers\ShopProductController.php:224` → `\shop\ProductSet::sets_list()`
|
||
|
||
**Plan:**
|
||
- `ProductSetRepository::allSets()` już istnieje — zamienić wywołanie na `$this->productSetRepo->allSets()`
|
||
- Wstrzyknąć `ProductSetRepository` do `ShopProductController` (jeśli jeszcze nie jest)
|
||
- Usunąć `class.ProductSet.php`
|
||
|
||
### 1.3 `class.Coupon.php` ✅ (~84 linii)
|
||
**Zewnętrzne użycia (3×):**
|
||
- `shop\class.Order.php:171` → `new \shop\Coupon($id)` (do przeniesienia w fazie Order)
|
||
- `front\Controllers\ShopOrderController.php:139` → `new \shop\Coupon($id)`
|
||
- `admin\Controllers\ShopOrderController.php:170` → `new \shop\Coupon($id)`
|
||
|
||
**Plan:**
|
||
- `CouponRepository::findByName()` / `find()` już istnieje — dodać `findById(int $id): ?array` jeśli brak
|
||
- Zamienić `new \shop\Coupon($id)` → `$this->couponRepo->findById($id)` w obu kontrolerach
|
||
- Użycie w `class.Order.php` — rozwiąże się w fazie Order
|
||
- Usunąć `class.Coupon.php`
|
||
|
||
---
|
||
|
||
## Faza 2 — Proste wrappery (minimalna logika do przeniesienia)
|
||
|
||
### 2.1 `class.Shop.php` ✅ (~39 linii) — utility cenowe
|
||
**Zewnętrzne użycia (~24× w szablonach):**
|
||
- `templates/shop-product/product.php`, `product-mini.php`
|
||
- `templates/shop-search/product-search.php`
|
||
- `templates/shop-basket/alert-product-sets.php`
|
||
- `templates/controls/alert-product-sets.php`
|
||
|
||
**Plan:**
|
||
- Przenieść `shortPrice()` i `isWholeNumber()` do `Shared\Helpers\Helpers` jako metody statyczne
|
||
- Zamienić `\shop\Shop::shortPrice()` → `\Shared\Helpers\Helpers::shortPrice()` we wszystkich szablonach (replace_all)
|
||
- Usunąć `class.Shop.php`
|
||
|
||
### 2.2 `class.Search.php` ✅ (~70 linii)
|
||
**Jedyne zewnętrzne użycie:**
|
||
- `front\LayoutEngine.php:337` → `\shop\Search::simple_form()`
|
||
|
||
**Plan:**
|
||
- Przenieść `simple_form()` do `front\Views\ShopSearch` (nowa klasa View, statyczna)
|
||
- Przenieść `search_results()` i `search_products()` do `front\Controllers\ShopSearchController` lub odpowiedniej istniejącej lokalizacji
|
||
- Zamienić wywołanie w `LayoutEngine`
|
||
- Usunąć `class.Search.php`
|
||
- Poprawić bug: `use shop\Produt;` (literówka — brakuje 'c')
|
||
|
||
### 2.3 `class.Basket.php` ✅ (~92 linii)
|
||
**Zewnętrzne użycia (6× w `ShopBasketController`):**
|
||
- `validate_basket()` — prosta walidacja tablicy sesji
|
||
- `check_product_quantity_in_stock()` — sprawdzenie stanów magazynowych
|
||
|
||
**Plan:**
|
||
- Przenieść obie metody do `Domain\Basket\BasketCalculator` (już istnieje)
|
||
- Zamienić wywołania w `ShopBasketController`
|
||
- Usunąć `class.Basket.php`
|
||
|
||
---
|
||
|
||
## Faza 3 — Klasy z logiką cache/atrybutów
|
||
|
||
### 3.1 `class.ProductCustomField.php` ✅ (~74 linii)
|
||
**Zewnętrzne użycia (2×):**
|
||
- `Domain\Order\OrderRepository.php:643` → `\shop\ProductCustomField::getFromCache()`
|
||
- `templates/shop-basket/_partials/product-custom-fields.php:3` → `\shop\ProductCustomField::getFromCache()`
|
||
|
||
**Plan:**
|
||
- Dodać `findCached(int $id): ?array` do `Domain\Product\ProductRepository` (lub nowy `ProductCustomFieldRepository`)
|
||
- Zamienić wywołania w OrderRepository i szablonie
|
||
- Usunąć `class.ProductCustomField.php`
|
||
|
||
### 3.2 `class.Category.php` ✅ (~84 linii)
|
||
**Zewnętrzne użycia (2×):**
|
||
- `index.php:225` → `\shop\Category::get_category_products_id()` (FB Pixel)
|
||
- `templates/shop-category/category.php:19` → `\shop\Category::get_subcategory_by_category()`
|
||
|
||
**Plan:**
|
||
- `CategoryRepository::productsId()` prawdopodobnie istnieje — sprawdzić i użyć lub dodać
|
||
- Dodać `subcategoriesCached(int $categoryId): array` do `CategoryRepository`
|
||
- Zamienić wywołania
|
||
- Usunąć `class.Category.php`
|
||
|
||
### 3.3 `class.ProductAttribute.php` ✅ (~119 linii)
|
||
**Zewnętrzne użycia (3 lokalizacje):**
|
||
- `shop\class.Product.php` — użycia wewnętrzne (rozwiążą się w fazie Product)
|
||
- `admin/templates/shop-product/product-combination.php:32` → `getAttributeName()`, `get_value_name()`
|
||
- `templates/shop-product/_partial/product-attribute.php:13` → `get_value_name()`
|
||
|
||
**Plan:**
|
||
- `AttributeRepository::getAttributeNameById()` i `getAttributeValueById()` już istnieją
|
||
- Dodać brakujące metody do `AttributeRepository`: `isValueDefault()`, `getAttributeOrder()`, `getAttributeNameByValue()` (z Redis cache)
|
||
- Zamienić wywołania w szablonach
|
||
- Usunąć `class.ProductAttribute.php`
|
||
|
||
---
|
||
|
||
## Faza 4 — Klasy z logiką promocji
|
||
|
||
### 4.1 `class.Promotion.php` ✅ (~107 linii)
|
||
**Zewnętrzne użycia (3 lokalizacje):**
|
||
- `front\Controllers\ShopBasketController.php` (6×) → `\shop\Promotion::find_promotion()`
|
||
- `admin\Controllers\ShopPromotionController.php` → `::$condition_type`, `::$discount_type`
|
||
|
||
**Plan:**
|
||
- Przenieść `get_active_promotions()` do `PromotionRepository` (z Redis cache)
|
||
- Przenieść `find_promotion()` do `PromotionRepository` — orkiestracja logiki applyType*
|
||
- Przenieść stałe `$condition_type` / `$discount_type` jako stałe klasowe do `PromotionRepository`
|
||
- Zamienić wywołania w `ShopBasketController` i `ShopPromotionController`
|
||
- Usunąć `class.Promotion.php`
|
||
|
||
---
|
||
|
||
## Faza 5 — Duże klasy z wieloma zależnościami
|
||
|
||
### 5.1 `class.Order.php` ✅ (~562 linii)
|
||
**Główna logika do migracji:**
|
||
- `OrderAdminService` ma już: `setOrderAsPaid()`, `setOrderAsUnpaid()`, `changeStatus()`, `resendConfirmationEmail()`, `saveOrderByAdmin()`, `deleteOrder()`, `saveNotes()`
|
||
- **Brakuje:** logika Apilo sync (`process_apilo_sync_queue()`, `sync_apilo_payment()`, `sync_apilo_status()`, queue management)
|
||
- **Brakuje:** `send_status_change_email()` (ale może być zintegrowane w `changeStatus()`)
|
||
|
||
**Plan:**
|
||
1. Sprawdzić, czy `OrderAdminService` obsługuje już Apilo sync (metoda `sendOrderToApilo()` istnieje)
|
||
2. Przenieść `process_apilo_sync_queue()` + kolejkę Apilo do `Domain\Integrations\ApiloService` (lub do `IntegrationsRepository`)
|
||
3. Przenieść `send_status_change_email()` do `OrderAdminService` (jeśli nie jest częścią `changeStatus()`)
|
||
4. Usunąć referencję do `new \shop\Coupon()` — użyć `CouponRepository` (z fazy 1.3)
|
||
5. Zaktualizować `cron.php` (jeśli woła `process_apilo_sync_queue`)
|
||
6. Usunąć `class.Order.php`
|
||
|
||
### 5.2 `class.Product.php` ✅ (~952 linii) — NAJWIĘKSZA KLASA
|
||
**Stan:** Większość logiki zmigowana do `ProductRepository`, ale klasa pełni rolę entity z ArrayAccess (używana w szablonach jako `$product['name']`).
|
||
|
||
**Metody do przeniesienia:**
|
||
- `getFromCache()` — cache wrappera entity → przenieść do `ProductRepository::findCached()`
|
||
- `getDefaultCombinationPrices()` → `ProductRepository`
|
||
- `generateSubtitleFromAttributes()` → `ProductRepository`
|
||
- `getProductDataBySelectedAttributes()` → `ProductRepository`
|
||
- `is_product_on_promotion()` → `ProductRepository`
|
||
- `product_sets_when_add_to_basket()` → `ProductSetRepository`
|
||
- `add_visit()` → `ProductRepository`
|
||
- `getProductImg()` / `getProductUrl()` → `ProductRepository`
|
||
- `searchProductsByName()` / `searchProductByNameAjax()` / `searchProductsByNameCount()` → `ProductRepository`
|
||
- `is_stock_0_buy()` → `ProductRepository`
|
||
- `get_product_permutation_quantity_options()` → `ProductRepository`
|
||
- `calculate_basket_product_price()` → `Domain\Basket\BasketCalculator`
|
||
- `get_product_id_by_attributes()` / `get_product_permutation_hash()` / `get_product_attributes()` → `ProductRepository` lub `AttributeRepository`
|
||
- `array_cartesian()` / `permutations()` → `ProductRepository` (helper prywatny)
|
||
- `generate_sku_code()` → `ProductRepository`
|
||
- `product_meta()` → `ProductRepository`
|
||
|
||
**Kluczowy problem:** szablony używają `$product['field']` przez ArrayAccess. Po migracji szablony będą dostawać zwykłe `array` z `ProductRepository` — to jest kompatybilne, bo `$product['field']` działa tak samo na tablicy.
|
||
|
||
**Plan:**
|
||
1. Etapami przenosić metody statyczne do `ProductRepository` / `AttributeRepository`
|
||
2. Przenieść `calculate_basket_product_price()` do `BasketCalculator`
|
||
3. Zamienić `Product::getFromCache()` → `ProductRepository::findCached()` (zwraca tablicę)
|
||
4. Zaktualizować szablony i kontrolery
|
||
5. Usunąć `class.Product.php`
|
||
|
||
---
|
||
|
||
## Pliki do modyfikacji (podsumowanie)
|
||
|
||
| Plik | Zmiany |
|
||
|------|--------|
|
||
| `autoload/Domain/Transport/TransportRepository.php` | + `allActiveOrdered()` |
|
||
| `autoload/Domain/Basket/BasketCalculator.php` | + `validateBasket()`, `checkStock()`, `calculateProductPrice()` |
|
||
| `autoload/Domain/Promotion/PromotionRepository.php` | + `getActivePromotions()`, `findPromotion()`, stałe |
|
||
| `autoload/Domain/Product/ProductRepository.php` | + ~15 metod z class.Product.php |
|
||
| `autoload/Domain/Attribute/AttributeRepository.php` | + `isValueDefault()`, `getAttributeOrder()`, `getAttributeNameByValue()` |
|
||
| `autoload/Domain/Category/CategoryRepository.php` | + `subcategoriesCached()`, `categoryProductIds()` |
|
||
| `autoload/Domain/Order/OrderAdminService.php` | + email statusu, sprawdzenie Apilo |
|
||
| `autoload/Domain/Integrations/` | + `ApiloSyncService` (queue Apilo) |
|
||
| `autoload/Domain/Coupon/CouponRepository.php` | + `findById()` |
|
||
| `autoload/Shared/Helpers/Helpers.php` | + `shortPrice()`, `isWholeNumber()` |
|
||
| `autoload/front/Views/ShopSearch.php` | NOWY — `simple_form()` |
|
||
| `autoload/front/LayoutEngine.php` | zamiana `\shop\Search` → `\front\Views\ShopSearch` |
|
||
| `autoload/front/Controllers/ShopBasketController.php` | zamiana `\shop\Basket`, `\shop\Promotion` |
|
||
| `autoload/admin/Controllers/ShopOrderController.php` | zamiana `\shop\Coupon`, `\shop\Transport` |
|
||
| `autoload/admin/Controllers/ShopProductController.php` | zamiana `\shop\ProductSet` |
|
||
| `autoload/admin/Controllers/ShopPromotionController.php` | zamiana `::$condition_type`, `::$discount_type` |
|
||
| `autoload/front/Controllers/ShopOrderController.php` | zamiana `\shop\Coupon` |
|
||
| Szablony (`templates/`) | zamiana `\shop\Shop::shortPrice()`, `\shop\ProductAttribute::*`, `\shop\Category::*` |
|
||
| `index.php` | zamiana `\shop\Category::get_category_products_id()` |
|
||
| `autoload/Domain/Order/OrderRepository.php` | zamiana `\shop\ProductCustomField` |
|
||
|
||
## Weryfikacja
|
||
|
||
Po każdej fazie:
|
||
1. Uruchomić `./test.ps1` — wszystkie 610+ testów muszą przechodzić
|
||
2. Grep po `\shop\` w całym codebase — upewnić się, że usunięta klasa nie jest nigdzie używana
|
||
3. Po zakończeniu wszystkich faz: `grep -r "\\\\shop\\\\" autoload/ templates/ admin/ index.php ajax.php cron.php api.php` powinien zwracać 0 wyników
|
||
|
||
## Szacunek złożoności
|
||
|
||
| Faza | Klasy | Linii do usunięcia | Złożoność |
|
||
|------|-------|---------------------|-----------|
|
||
| 1 | Transport, ProductSet, Coupon | ~164 | Niska |
|
||
| 2 | Shop, Search, Basket | ~201 | Niska-średnia |
|
||
| 3 | ProductCustomField, Category, ProductAttribute | ~277 | Średnia |
|
||
| 4 | Promotion | ~107 | Średnia |
|
||
| 5 | Order, Product | ~1 514 | Wysoka |
|
||
| **Razem** | **12 klas** | **~2 363** | |
|