Files
shopPRO/docs/FRONTEND_REFACTORING_PLAN.md
Jacek Pyziak 782dd35d5b ver. 0.278: Settings + Languages frontend migration, bug fix get_single_settings_value
- Add cached frontend methods to existing Domain repositories (allSettings, getSingleValue, defaultLanguage, activeLanguages, translations)
- Convert front\factory\Settings and Languages to facades delegating to Domain repositories
- Fix get_single_settings_value() - was hardcoded to 'firm_name', now uses $param correctly
- Add CacheHandler stub methods (get/set/exists) to test bootstrap
- Establish architectural rule: Domain classes are shared between admin and frontend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 13:50:27 +01:00

479 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan refaktoryzacji frontendu shopPRO
## Kontekst
Panel administratora (33 moduły) został w pełni zmigrowany na architekturę Domain + DI + Controllers (ver. 0.2370.277). Teraz kolej na frontend: klasy `front/`, `shop/`, `cms/`, klasy utility z `autoload/` oraz szablony `templates/`.
**Cel:** przenieść logikę biznesową z warstwy frontendowej do warstwy domenowej (`Domain/`), zachowując kompatybilność wsteczną przez fasady. Zastosować te same wzorce co w adminie (DI, repozytoria/serwisy, testy PHPUnit).
---
## Inwentaryzacja — co istnieje
### front/controls/ (8 klas — handlery requestów)
| Klasa | Status | Logika biznesowa |
|-------|--------|-----------------|
| Site | Router główny | route(), check_url_params(), title() |
| ShopBasket | MIXED | Operacje koszyka, add/remove/quantity, checkout |
| ShopClient | Fasada | Deleguje do factory |
| ShopOrder | KRYTYCZNY | Webhooki płatności (tPay, Przelewy24, Hotpay) — bezpośrednie operacje DB |
| ShopProduct | Fasada | lazy_loading, warehouse_message, draw_product_attributes |
| ShopProducer | Fasada | list(), products() |
| ShopCoupon | Fasada | use_coupon(), delete_coupon() |
| Newsletter | Fasada | signin(), confirm(), unsubscribe() |
### front/factory/ (20 klas — pobieranie danych + logika)
| Klasa | Status | Priorytet migracji |
|-------|--------|--------------------|
| ShopProduct | ORYGINALNA LOGIKA (~370 linii) | KRYTYCZNY — product_details(), promoted/top/new products |
| ShopOrder | ORYGINALNA LOGIKA (~180 linii) | KRYTYCZNY — basket_save() tworzy zamówienie |
| ShopClient | ORYGINALNA LOGIKA | KRYTYCZNY — login (BUG: hardcoded bypass 'Legia1916'), signup, recover |
| ShopCategory | ORYGINALNA LOGIKA | WYSOKI — złożone SQL z language fallback |
| Articles | ORYGINALNA LOGIKA | WYSOKI — złożone SQL z language fallback |
| ShopPromotion | ORYGINALNA LOGIKA | WYSOKI — silnik promocji (5 typów) |
| ShopBasket | Fasada | ŚREDNI — summary_price, count |
| ShopTransport | CZĘŚCIOWO zmigrowana | ŚREDNI — transport_methods z filtrowaniem |
| ShopPaymentMethod | ZMIGROWANA (Domain) | — |
| ShopStatuses | ZMIGROWANA (Domain) | — |
| Scontainers | ZMIGROWANA (Domain) | — |
| Newsletter | CZĘŚCIOWO zmigrowana | ŚREDNI |
| Settings | Fasada (BUG: get_single_settings_value ignoruje $param) | NISKI |
| Languages | Fasada | NISKI |
| Layouts | Fasada | NISKI |
| Banners | Fasada | NISKI |
| Menu | Fasada | NISKI |
| Pages | Fasada | NISKI |
| ShopAttribute | Fasada | NISKI |
| ShopCoupon | Model danych | NISKI |
### front/view/ (12 klas — renderowanie)
| Klasa | Status |
|-------|--------|
| Site | KRYTYCZNY — show() ~600 linii, pattern substitution engine |
| ShopCategory | VIEW z logiką routingu (infinite scroll vs pagination) |
| Articles, Banners, Languages, Menu, Newsletter, Scontainers | Czyste VIEW |
| ShopClient, ShopOrder, ShopPaymentMethod | Czyste VIEW |
| ShopTransport | PUSTA klasa (placeholder) |
### shop/ (14 klas — encje biznesowe)
| Klasa | Linii | Status | Priorytet |
|-------|-------|--------|-----------|
| Product | ~950 | KRYTYCZNY — pricing, atrybuty, search, cache, permutacje | KRYTYCZNY |
| Order | ~500+ | KRYTYCZNY — statusy, Apilo sync, emaile, webhooki | KRYTYCZNY |
| Promotion | ~200 | WYSOKI — matching aktywnych promocji + delegacja do factory | WYSOKI |
| Basket | ~80 | ŚREDNI — walidacja stanów magazynowych | ŚREDNI |
| Category | ~60 | NISKI — proste zapytania, pusty konstruktor | NISKI |
| Search | ~80 | NISKI — wrapper na szablony | NISKI |
| Coupon | ~60 | NISKI — niekompletne metody | NISKI |
| Transport | ~30 | NISKI — transport_list() | NISKI |
| PaymentMethod | — | ZMIGROWANA (fasada do Domain) | — |
| Producer | — | ZMIGROWANA (fasada do Domain) | — |
| ProductSet | — | ZMIGROWANA (fasada do Domain) | — |
| ProductAttribute | ~100 | OK — dobry caching | — |
| ProductCustomField | ~50 | OK — Redis caching | — |
| Shop | ~30 | OK — utility do cen | — |
### autoload/ root (klasy utility)
| Klasa | Linii | Status |
|-------|-------|--------|
| S | ~1130 | htacces() ~500 linii — generowanie .htaccess; reszta to utility |
| Tpl | ~90 | OK — silnik szablonów, bez zmian |
| CacheHandler | ~50 | OK — Redis wrapper |
| RedisConnection | ~40 | OK — singleton |
| Email | ~100 | OK — PHPMailer wrapper (drobne poprawki) |
| Log | ~20 | OK — audit logging |
| DbModel | ~60 | OK — base ORM |
| Cache | ~50 | LEGACY — file-based cache, rozważyć usunięcie |
| Html | ~80 | OK — form helpers |
| Image | ~100 | OK — GD wrapper |
| Mobile_Detect | — | Third-party, bez zmian |
### cms/ (1 klasa)
| Klasa | Status |
|-------|--------|
| Layout | BUG w __get() — referuje $this->data które nie istnieje |
### templates/ (75 plików w 15 modułach)
articles(8), banner(2), controls(1), menu(4), newsletter(2), scontainers(1), shop-basket(9), shop-category(6), shop-client(8), shop-coupon(1), shop-order(3), shop-producer(3), shop-product(12), shop-search(3), site(11)
### Entry points
- `index.php` — główny frontend (autoload, sesja, DB, routing, DOM post-processing)
- `ajax.php` — AJAX handler (koszyk, transport, kontakt)
- `api.php` — REST API (Ekomi CSV export)
- `download.php` — pobieranie plików
- `cron-turstmate.php` — TrustMate integracja
---
## Znane bugi do naprawy podczas refaktoringu
1. **KRYTYCZNY** `front\factory\ShopClient::login()` — hardcoded password bypass `'Legia1916'`
2. `front\factory\Settings::get_single_settings_value()` — ignoruje `$param`, zawsze zwraca `firm_name`
3. `front\factory\Newsletter::newsletter_unsubscribe()` — błędna składnia SQL w delete
4. `cms\Layout::__get()` — referuje nieistniejące `$this->data`
5. `shop\Search` — typo w use: `shop\Produt` (brak 'c')
---
## Kolejność migracji (graf zależności)
```
Settings, Languages (liście — brak zależności)
Banners, Menu, Pages, Articles, Layouts (zależą od Languages)
Category (zależy od Languages)
Promotion (zależy od Category — rozbicie circular dep)
Product Frontend (zależy od Category, Promotion, Languages)
Client/Auth (standalone + security fix)
Transport, Payment, Coupon (frontend serwisy)
Basket (zależy od Product, Promotion, Transport, Coupon)
Order Creation (zależy od Basket, Product, Transport, Payment)
Payment Webhooks (zależy od Order)
shop\Order instance + Apilo (zależy od Order Operations)
shop\Product instance + cache (zależy od ProductFrontendService)
Frontend App + Controllers (nowa warstwa DI)
Site Layout Engine (zależy od wszystkiego)
Entry Point Unification (index.php, ajax.php)
Legacy Cleanup
```
---
## Etapy migracji
### Etap: Settings + Languages — ZREALIZOWANY
**Cel:** Dodac metody frontendowe (z cache Redis) do istniejacych repozytoriow Domain.
**DODANE METODY (do istniejacych klas):**
- `Domain/Settings/SettingsRepository``allSettings($skipCache)`, `getSingleValue($param)` (FIX: uzywa poprawnie $param)
- `Domain/Languages/LanguagesRepository``defaultLanguage()`, `activeLanguages()`, `translations($lang)`
- Testy: dopisane do `SettingsRepositoryTest` (6 testow), `LanguagesRepositoryTest` (7 testow)
**ZMIANA:**
- `front/factory/Settings` → fasada delegujaca do `SettingsRepository`
- `front/factory/Languages` → fasada delegujaca do `LanguagesRepository`
- `tests/bootstrap.php` — uzupelniony stub CacheHandler o `get()`/`set()`/`exists()`
**BUG FIX:** `get_single_settings_value()` — zmiana `['param' => 'firm_name']` na `['param' => $param]`
---
### Etap: Category Frontend Service
**Cel:** Migracja `front\factory\ShopCategory` do Domain.
**NOWE:**
- `Domain/Category/CategoryFrontendService.php``getCategorySort()`, `categoryName()`, `categoryUrl()`, `blogCategoryProducts()`, `categoryProducts()`, `productsId()` (złożone SQL z sortowaniem/language fallback/paginacją), `categoryDetails()`, `categoriesDetails()` (rekurencyjne), `categoryProductsCount()`
- Testy: `CategoryFrontendServiceTest`
**ZMIANA:**
- `front/factory/ShopCategory` → fasada
- `front/factory/ShopProduct::product_categories()` → deleguje do `CategoryFrontendService`
---
### Etap: Banners, Menu, Pages, Articles, Layouts Frontend Services
**Cel:** Migracja pozostałych fabryk "liściowych".
**NOWE:**
- `Domain/Banner/BannerFrontendService.php``mainBanner()`, `banners()` (filtrowanie po datach)
- `Domain/Menu/MenuFrontendService.php``menuDetails()`, `menuPages()` (rekurencja)
- `Domain/Pages/PagesFrontendService.php``pageDetails()`, `mainPageId()`, `langUrl()`, `pageSort()`
- `Domain/Article/ArticleFrontendService.php``articleDetails()`, `news()`, `pageArticles()`, `pageArticlesCount()`, `generateTableOfContents()`, `generateHeadersIds()`
- `Domain/Layouts/LayoutsFrontendService.php``activeLayout()`, `articleLayout()`, `productLayout()`, `categoryLayout()`, `defaultLayout()`, `categoryDefaultLayout()`
- Testy: 5 plików testowych
**ZMIANA:**
- `front/factory/Banners`, `Menu`, `Pages`, `Articles`, `Layouts` → fasady
**BUG FIX:** `cms\Layout::__get()` — poprawka referencji do `$this->data`
---
### Etap: Promotion Engine (rozbicie circular dependency)
**Cel:** Przeniesienie silnika promocji do Domain. Rozbicie cyklicznej zależności `shop\Promotion ↔ front\factory\ShopPromotion`.
**Obecna zależność cykliczna:**
```
shop\Promotion::find_promotion() → front\factory\ShopPromotion::promotion_type_XX()
front\factory\ShopPromotion::promotion_type_XX() → shop\Product::is_product_on_promotion()
```
**Rozwiązanie:** Wszystko w jednym serwisie `PromotionFrontendService`.
**NOWE:**
- `Domain/Promotion/PromotionFrontendService.php`:
- `getActivePromotions()` (z `shop\Promotion`)
- `applyPromotions(array $basket)` (z `shop\Promotion::find_promotion()`)
- `private applyType01..05()` (z `front\factory\ShopPromotion`)
- `private isProductOnPromotion()` (z `shop\Product` — proste zapytanie)
- Testy: `PromotionFrontendServiceTest` (7 typów promocji + brak aktywnych)
**ZMIANA:**
- `shop/Promotion` → fasada do `PromotionFrontendService::applyPromotions()`
- `front/factory/ShopPromotion` → fasada
---
### Etap: Product Frontend Service
**Cel:** Migracja `front\factory\ShopProduct` i statycznych metod `shop\Product` do Domain.
**NOWE:**
- `Domain/Product/ProductFrontendService.php`:
- Z `front\factory\ShopProduct`: `productDetails()` (~330 linii z Redis), `promotedProducts()`, `topProducts()`, `newProducts()`, `productUrl()`, `getMinimalPrice()`, `isProductActive()`, `getProductSku()`, `getProductEan()`, `productImage()`, `productWp()`, `randomProducts()`, `warehouseMessageZero()`, `warehouseMessageNonzero()`
- Z `shop\Product` (statyczne): `isProductOnPromotion()`, `getProductImg()`, `getProductUrl()`, `addVisit()`, `productSetsForBasket()`, `searchProductsByName()`, `searchProductByNameAjax()`
- Testy: `ProductFrontendServiceTest`
**ZMIANA:**
- `front/factory/ShopProduct` → fasada
- `shop/Product` statyczne metody → fasady do `ProductFrontendService`
**UWAGA:** Konstruktor `shop\Product`, `getFromCache()`, `calculate_basket_product_price()` — zostają na razie (instancyjne, używane w szablonach). Migracja w etapie "Product Instance + Cache".
---
### Etap: Client Authentication (Security Fix)
**Cel:** Migracja `front\factory\ShopClient` + NAPRAWIENIE hardcoded password bypass.
**NOWE:**
- `Domain/Client/ClientFrontendService.php`:
- `login()`**BEZ** bypassa 'Legia1916', z opcjonalną migracją md5 → password_hash
- `signup()`, `registerConfirm()`, `sendPasswordRecovery()`, `resetPassword()`
- `clientDetails()`, `clientOrders()`
- CRUD adresów: `saveAddress()`, `deleteAddress()`, `getAddresses()`, `markAddressAsCurrent()`
- Testy: `ClientFrontendServiceTest`**KRYTYCZNY test: login z 'Legia1916' NIE przechodzi**
**ZMIANA:**
- `front/factory/ShopClient` → fasada
- `front/controls/ShopClient` → deleguje do serwisu
---
### Etap: Transport, Payment, Coupon Frontend Services
**Cel:** Frontend serwisy dla transportu, płatności i kuponów.
**NOWE:**
- `Domain/Transport/TransportFrontendService.php``transportMethods()` (filtrowanie WP, free delivery), `transportCost()`, `transportDetails()`
- `Domain/PaymentMethod/PaymentMethodFrontendService.php``activePaymentMethods()`, `paymentMethodsByTransport()`, `paymentMethodDetails()`
- `Domain/Coupon/CouponFrontendService.php``validateCoupon()`, `applyCoupon()`, `markCouponUsed()`
- Testy: 3 pliki testowe
**ZMIANA:**
- `front/factory/ShopTransport` → fasada
- `front/factory/ShopCoupon` → fasada
- `shop/Coupon` → fasada
- `shop/Transport` → fasada
---
### Etap: Basket Service
**Cel:** Migracja logiki koszyka do `Domain\Basket\BasketService`.
**NOWE:**
- `Domain/Basket/BasketService.php`:
- `addProduct()`, `removeProduct()`, `changeQuantity()`, `increaseQuantity()`, `decreaseQuantity()`
- `summaryPrice()`, `countProducts()`, `countProductsText()`
- `validateStock()` (z `shop\Basket::check_product_quantity_in_stock()`)
- `calculateProductPrice()` (z `shop\Product::calculate_basket_product_price()` ~112 linii)
- Testy: `BasketServiceTest` (pricing z promocjami/kuponami, walidacja stanów)
**ZMIANA:**
- `front/factory/ShopBasket` → fasada
- `front/controls/ShopBasket` — wewnętrznie używa `BasketService`
- `shop/Basket` → fasada
- `shop/Product::calculate_basket_product_price()` → fasada
---
### Etap: shop\Product Instance + Cache
**Cel:** Refaktoring konstruktora `shop\Product` i `getFromCache()`.
**NOWE:**
- `Domain/Product/ProductDataLoader.php`:
- `loadFull(int $productId, ?string $langId, ?string $permutationHash)` — ładowanie pełnych danych produktu
- `loadCached()` — Redis cache wrapper
- Testy: `ProductDataLoaderTest`
**ZMIANA:**
- `shop/Product` — konstruktor i `getFromCache()` delegują do `ProductDataLoader`
- ArrayAccess interface zachowany (szablony dalej używają `$product->property`)
---
### Etap: Order Creation Frontend Service
**Cel:** Migracja `front\factory\ShopOrder::basket_save()` (~180 linii).
**NOWE:**
- `Domain/Order/OrderFrontendService.php`:
- `createOrder()` — tworzenie zamówienia z koszyka (walidacja, kalkulacja cen, insert, redukcja stanów, obsługa kuponu, wysyłka emaili, auto-status dla pobrania)
- `generateOrderNumber()` — format YYYY/MM/NNN
- `orderDetails()`, `orderIdByHash()`, `orderHashById()`
- Testy: `OrderFrontendServiceTest`
**ZMIANA:**
- `front/factory/ShopOrder` → fasada
---
### Etap: Payment Webhook Service
**Cel:** Wyodrębnienie webhooków płatności z `front\controls\ShopOrder`.
**NOWE:**
- `Domain/Payment/PaymentWebhookService.php`:
- `processTpay(array $params)` — weryfikacja tPay
- `processPrzelewy24(array $params)` — weryfikacja przez API + walidacja kwoty
- `processHotpay(array $params)` — walidacja SHA256 hash
- `private markOrderPaid()` — wspólna logika (update status + email + Apilo sync)
- Testy: `PaymentWebhookServiceTest`
**ZMIANA:**
- `front/controls/ShopOrder` — webhooki stają się thin wrappers
**POPRAWA:** Zamiana `file_put_contents('tpay.txt')` na `\Log::save_log()`
---
### Etap: shop\Order Instance + Apilo Service
**Cel:** Refaktoring `shop\Order` instancyjnych metod + wyodrębnienie integracji Apilo.
**NOWE:**
- `Domain/Integrations/ApiloService.php`:
- `syncPayment()`, `syncStatus()`, `processQueue()`
- `private queueSync()`, `private loadQueue()`, `private saveQueue()`
- `Domain/Order/OrderOperationsService.php`:
- `setAsPaid()`, `setAsUnpaid()`, `updateStatus()`, `sendStatusChangeEmail()`, `resendConfirmationEmail()`
- Testy: `ApiloServiceTest`, `OrderOperationsServiceTest`
**ZMIANA:**
- `shop/Order` instancyjne metody → fasady do `OrderOperationsService`
- Konstruktor i ArrayAccess bez zmian
---
### Etap: Frontend App + Controllers (DI layer)
**Cel:** Stworzenie `front\App` (wzorowanego na `admin\App`) z mapą kontrolerów i DI.
**NOWE:**
- `autoload/front/App.php``render()`, `handleAjax()`, `getControllerFactories()` (DI wiring)
- `autoload/front/Controllers/ShopBasketController.php` — DI z `BasketService`, `ProductFrontendService`
- `autoload/front/Controllers/ShopOrderController.php` — DI z `OrderFrontendService`, `PaymentWebhookService`
- `autoload/front/Controllers/ShopClientController.php` — DI z `ClientFrontendService`
- Testy: 3 pliki testowe kontrolerów
**ZMIANA:**
- `front/controls/ShopBasket`, `ShopOrder`, `ShopClient` → delegują do nowych kontrolerów
---
### Etap: Site Layout Engine
**Cel:** Refaktoring `front\view\Site::show()` (~600 linii) na testowalny `LayoutEngine`.
**NOWE:**
- `autoload/front/View/LayoutEngine.php`:
- `resolveLayout(array $page, array $params)` — wybór layoutu (product > category > article > page > default)
- `processPatterns(string $html, array $context)` — zamiana tagów szablonowych
- Osobne prywatne metody: `replaceCategories()`, `replaceProducts()`, `replaceMenus()`, `replaceLanguageTags()`, `replaceBanners()`, `replaceArticles()`, `replaceContainers()`, `replaceMetaTags()`, etc.
- Testy: `LayoutEngineTest`
**ZMIANA:**
- `front/view/Site::show()` → thin wrapper na `LayoutEngine`
---
### Etap: Entry Point Unification
**Cel:** Ujednolicenie bootstrapu `index.php` i `ajax.php`.
**NOWE:**
- `autoload/front/Bootstrap.php``init()` (autoloader, config, sesja, DB, lang/settings)
- `autoload/front/PostProcessor.php` — ekstrakcja DOM manipulacji z `index.php` (GTM, Facebook Pixel, WebP, lazy loading)
**ZMIANA:**
- `index.php` → uproszczony: `Bootstrap::init()``App::render()``PostProcessor::process()`
- `ajax.php` → uproszczony: `Bootstrap::init()``App::handleAjax()`
---
### Etap: Legacy Cleanup
**Cel:** Finalne porządki.
**USUNIĘCIE:**
- Martwy kod `eval()` dla `[PHP]` bloków (jeśli nieużywany)
- `class.Cache.php` (legacy file-based) jeśli wszystkie użycia przeniesione na `CacheHandler`
- Pusta klasa `front\view\ShopTransport`
**ZMIANA:**
- Dodanie `@deprecated` do wszystkich metod-fasad w `front/factory/`, `front/controls/`, `shop/`
- PHPDoc do wszystkich nowych klas Domain z `@since`
- Aktualizacja `tests/bootstrap.php`
- BUG FIX: `shop\Search` — typo `shop\Produt``shop\Product`
- BUG FIX: `front\factory\Newsletter::newsletter_unsubscribe()` — poprawka SQL
---
## Podsumowanie
| Etap | Zakres | Priorytet | Nowe klasy Domain | Testy |
|------|--------|-----------|-------------------|-------|
| Settings + Languages | Fundamenty | FUNDAMENT | 2 serwisy | 2 |
| Category Frontend | Kategorie | WYSOKI | 1 serwis | 1 |
| Banners/Menu/Pages/Articles/Layouts | Treści | ŚREDNI | 5 serwisów | 5 |
| Promotion Engine | Promocje | KRYTYCZNY | 1 serwis | 1 |
| Product Frontend | Produkty | KRYTYCZNY | 1 serwis | 1 |
| Client/Auth (security fix) | Klienci | KRYTYCZNY | 1 serwis | 1 |
| Transport/Payment/Coupon | Dostawa/Płatności | WYSOKI | 3 serwisy | 3 |
| Basket Service | Koszyk | WYSOKI | 1 serwis | 1 |
| Product Instance + Cache | Produkt cache | ŚREDNI | 1 loader | 1 |
| Order Creation | Zamówienia | WYSOKI | 1 serwis | 1 |
| Payment Webhooks | Webhooki | WYSOKI | 1 serwis | 1 |
| Order Instance + Apilo | Zamówienie + Apilo | ŚREDNI | 2 serwisy | 2 |
| Frontend App + Controllers | DI layer | WYSOKI | App + 3 kontrolery | 3 |
| Layout Engine | Silnik layoutu | ŚREDNI | 1 engine | 1 |
| Entry Point Unification | Entry points | ŚREDNI | Bootstrap + PostProcessor | 1 |
| Legacy Cleanup | Porządki | NISKI | — | — |
**Łącznie:** 16 etapów, ~24 nowe klasy Domain, ~25 plików testowych
### Wzorce do przestrzegania (z migracji admin)
- Konstruktor DI z `$db` (medoo)
- CacheHandler (Redis) dla cachingu — unifikacja (usunięcie starego `Cache`)
- Fasady w `shop\*` i `front\factory\*` dla kompatybilności wstecznej
- Testy PHPUnit z mockami medoo
- Namespace `\Domain\*``autoload/Domain/*/`
- Namespace `\front\Controllers\``autoload/front/Controllers/`
- **Klasy Domain sa wspolne dla admin i frontendu** — NIE tworzymy osobnych FrontendService/AdminService. Metody frontendowe (z cache Redis) dodajemy do istniejacych repozytoriow/serwisow Domain. Klasy sa ladowane lazy (instancja tworzona dopiero przy wywolaniu), wiec nie wplywaja na wydajnosc.
### Weryfikacja po każdym etapie
1. `composer test` (pełny suite PHPUnit)
2. Manualne sprawdzenie frontendu: strona główna, kategoria, produkt, koszyk, zamówienie
3. Sprawdzenie AJAX: dodawanie do koszyka, zmiana transportu, kupon
4. Sprawdzenie webhooków płatności (tPay, Przelewy24, Hotpay)