ver. 0.294: Remove all 12 legacy autoload/shop/ classes (~2363 lines)

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>
This commit is contained in:
2026-02-18 02:05:39 +01:00
parent c72ba388a0
commit e1cb421aaf
74 changed files with 2176 additions and 2669 deletions

View File

@@ -4,6 +4,35 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
---
## ver. 0.294 (2026-02-18) - Usuniecie autoload/shop/ — 12 legacy klas
- **Faza 5.1: class.Order.php (~562 linii) USUNIETA**
- Logika Apilo sync przeniesiona do `OrderAdminService::processApiloSyncQueue()`
- Email zmiany statusu zintegrowany w `OrderAdminService::changeStatus()`
- `cron.php` przepiety na `OrderAdminService`
- `front\Controllers\ShopOrderController` — dodano `OrderAdminService` jako drugie DI
- `\shop\Order::order_statuses()``OrderRepository::orderStatuses()` (3 lokalizacje)
- **Faza 5.2: class.Product.php (~952 linii) USUNIETA — NAJWIEKSZA KLASA**
- ~20 metod przeniesionych do `ProductRepository`: `findCached()`, `isProductOnPromotion()`, `productSetsWhenAddToBasket()`, `addVisit()`, `getProductImg()`, `getProductUrl()`, `searchProductsByName()`, `searchProductByNameAjax()`, `searchProductsByNameCount()`, `isStock0Buy()`, `getProductPermutationQuantityOptions()`, `getProductIdByAttributes()`, `getProductPermutationHash()`, `getProductAttributes()`, `generateSkuCode()`, `productMeta()`, `generateSubtitleFromAttributes()`, `getDefaultCombinationPrices()`, `getProductDataBySelectedAttributes()`, `productCategories()`, `arrayCartesian()`
- `calculate_basket_product_price()` przeniesione do `BasketCalculator::calculateBasketProductPrice()`
- `BasketCalculator` przepisany: `summaryPrice()`, `checkProductQuantityInStock()` uzywaja `ProductRepository`
- XML generation (4 metody) — konwersja object→array access
- ~60+ callsite'ow zamienionych w kontrolerach, szablonach, repozytoriach i entry-pointach
- Cache key strings zaktualizowane z `\shop\Product::` na `ProductRepository::`
- **Cleanup:**
- Usunieto dead `\shop\` routing z `front\App::route()`
- Cache key `\shop\Promotion::get_active_promotions``PromotionRepository::getActivePromotions`
- Katalog `autoload/shop/` jest teraz pusty
- **Production hotfixes:**
- FIX: `findCached()` — stale Redis cache z obiektami `\shop\Product` powodowal ceny 0,00 zl (obiekty `__PHP_Incomplete_Class` po `(array)` cast maja zmanglowane klucze). Teraz invaliduje stary cache i re-fetchuje z DB
- FIX: `LayoutEngine.php` — 4 niekwalifikowane `Product::getFromCache()` resolwaly do `\front\Product` po usunieciu `use shop\Product`
- FIX: szablony (product-mini, product-search, product, product-warehouse-message, alert-product-sets, basket-details, main-view, promoted-products) — konwersja object access (`$product->field`) na array access (`$product['field']`)
- UPDATE: `AttributeRepository::getAttributeValueById()` — dodano Redis cache (zgodnosc ze starym `\shop\ProductAttribute::get_value_name()`)
- **PODSUMOWANIE CALEJ MIGRACJI:** 12 legacy klas, ~2363 linii kodu usunieto. Zero referencji `\shop\` w aktywnym kodzie.
- Testy: 610 OK, 1817 asercji
---
## ver. 0.293 (2026-02-17) - front\controls\Site + front\view\Site → front\App + front\LayoutEngine
- **front\controls\Site → front\App** — migracja routera na nowy namespace

View File

@@ -0,0 +1,234 @@
# 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** | |

View File

@@ -38,23 +38,23 @@ Dokumentacja struktury projektu shopPRO do szybkiego odniesienia.
```
shop\product:{product_id}:{lang_id}:{permutation_hash}
```
- Przechowuje zserializowany obiekt produktu
- Przechowuje tablicę danych produktu (z kombinacjami, obrazkami, producentem itd.)
- TTL: 24 godziny (86400 sekund)
- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121`
- Klasa: `Domain\Product\ProductRepository::findCached()` - `autoload/Domain/Product/ProductRepository.php`
#### Opcje ilościowe produktu
```
\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation}
ProductRepository::getProductPermutationQuantityOptions:v2:{product_id}:{permutation}
```
- Przechowuje informacje o ilości i komunikatach magazynowych
- Klasa: `shop\Product::get_product_permutation_quantity_options()` - `autoload/shop/class.Product.php:549`
- Klasa: `Domain\Product\ProductRepository::getProductPermutationQuantityOptions()` - `autoload/Domain/Product/ProductRepository.php`
#### Zestawy produktów
```
\shop\Product::product_sets_when_add_to_basket:{product_id}
ProductRepository::productSetsWhenAddToBasket:{product_id}
```
- Przechowuje produkty często kupowane razem
- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316`
- Klasa: `Domain\Product\ProductRepository::productSetsWhenAddToBasket()` - `autoload/Domain/Product/ProductRepository.php`
## Integracje z systemami zewnętrznymi (CRON)
@@ -179,12 +179,12 @@ Główna klasa helper (przeniesiona z `class.S.php`) z metodami:
- ~~`\front\factory\`~~ - USUNIĘTY — wszystkie fabryki zmigrowane do Domain
- ~~`\front\controls\`~~ - USUNIĘTY — router przeniesiony do `\front\App`
- ~~`\front\view\`~~ - USUNIĘTY — layout engine przeniesiony do `\front\LayoutEngine`
- `\shop\` - klasy sklepu (Product, Order, itp.)
- ~~`\shop\`~~ - USUNIĘTY — wszystkie klasy zmigrowane do `\Domain\`
### Cachowanie produktów
```php
// Pobranie produktu z cache
$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash);
$product = (new \Domain\Product\ProductRepository($mdb))->findCached($product_id, $lang_id, $permutation_hash);
// Czyszczenie cache produktu
\Shared\Helpers\Helpers::clear_product_cache($product_id);
@@ -264,9 +264,9 @@ autoload/
- `admin\factory\ShopTransport` i `front\factory\ShopTransport` przepiete na repozytorium.
**Aktualizacja 2026-02-14 (ver. 0.270):**
- `shop\Order` zapisuje nieudane syncy Apilo (status/platnosc) do kolejki `temp/apilo-sync-queue.json`.
- `cron.php` automatycznie ponawia zalegle syncy (`Order::process_apilo_sync_queue()`).
- `shop\Order::set_as_paid()` wysyla mapowany typ platnosci Apilo (z mapowania metody platnosci), bez stalej wartosci `type`.
- `OrderAdminService` zapisuje nieudane syncy Apilo (status/platnosc) do kolejki `temp/apilo-sync-queue.json`.
- `cron.php` automatycznie ponawia zalegle syncy (`OrderAdminService::processApiloSyncQueue()`).
- `OrderAdminService::setOrderAsPaid()` wysyla mapowany typ platnosci Apilo (z mapowania metody platnosci), bez stalej wartosci `type`.
**Aktualizacja 2026-02-15 (ver. 0.276):**
- Dodano modul domenowy `Domain/Order/OrderRepository.php`.

View File

@@ -33,10 +33,17 @@ Alternatywnie (Git Bash):
## Aktualny stan suite
Ostatnio zweryfikowano: 2026-02-17
Ostatnio zweryfikowano: 2026-02-18
```text
OK (610 tests, 1816 assertions)
OK (610 tests, 1817 assertions)
```
Aktualizacja po usunieciu autoload/shop/ — 12 legacy klas (2026-02-18, ver. 0.294):
```text
Pelny suite: OK (610 tests, 1817 assertions)
Zmodyfikowane testy: PromotionRepositoryTest (cache key \shop\Promotion → PromotionRepository)
Zmodyfikowane testy: ShopOrderControllerTest (zamiana \shop\Coupon → CouponRepository DI)
```
Aktualizacja po migracji front\controls\Site + front\view\Site (2026-02-17, ver. 0.293):

View File

@@ -18,16 +18,16 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią
## Procedura tworzenia nowej aktualizacji
## Status biezacej aktualizacji (ver. 0.293)
## Status biezacej aktualizacji (ver. 0.294)
- Wersja udostepniona: `0.293` (data: 2026-02-17).
- Wersja udostepniona: `0.294` (data: 2026-02-18).
- Pliki publikacyjne:
- `temp/update_build/ver_0.293.zip`, `ver_0.293_files.txt`
- `temp/update_build/ver_0.294.zip`, `ver_0.294_files.txt`
- Pliki metadanych aktualizacji:
- `updates/changelog.php` (dodany wpis `ver. 0.293`)
- `updates/versions.php` (`$current_ver = 293`)
- `updates/changelog.php` (dodany wpis `ver. 0.294`)
- `updates/versions.php` (`$current_ver = 294`)
- Weryfikacja testow przed publikacja:
- `OK (610 tests, 1816 assertions)`
- `OK (610 tests, 1817 assertions)`
### 1. Określ numer wersji
Sprawdź ostatnią wersję w `updates/` i zwiększ o 1.