# Plan Refaktoryzacji shopPRO - Domain-Driven Architecture ## Cel Stopniowe przeniesienie logiki biznesowej do architektury warstwowej: - **Domain/** - logika biznesowa (core) - **Admin/** - warstwa administratora - **Frontend/** - warstwa użytkownika - **Shared/** - współdzielone narzędzia ## Docelowa struktura ``` autoload/ ├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\ │ ├── Product/ │ │ ├── ProductRepository.php # ✅ Zmigrowane (getQuantity, getPrice, getName, find, updateQuantity) │ │ ├── ProductService.php # Logika biznesowa (przyszłość) │ │ └── ProductCacheService.php # Cache produktu (przyszłość) │ ├── Banner/ │ │ └── BannerRepository.php # ✅ Zmigrowane (find, delete, save) │ ├── Settings/ │ │ └── SettingsRepository.php # ✅ Zmigrowane (saveSettings, getSettings) - fasada → factory │ ├── Cache/ │ │ └── CacheRepository.php # ✅ Zmigrowane (clearCache) │ ├── Order/ │ ├── Category/ │ └── ... │ ├── admin/ # Warstwa administratora (istniejący katalog!) │ ├── Controllers/ # Nowe kontrolery - namespace \admin\Controllers\ │ │ ├── BannerController.php │ │ └── SettingsController.php │ ├── controls/ # Stare kontrolery (legacy fallback) │ ├── factory/ # Stare helpery (legacy) │ └── view/ # Widoki (statyczne - OK bez zmian) │ ├── Frontend/ # Warstwa użytkownika (przyszłość) │ ├── Controllers/ │ └── Services/ │ ├── Shared/ # Współdzielone narzędzia │ ├── Cache/ │ │ ├── CacheHandler.php │ │ └── RedisConnection.php │ └── Helpers/ │ └── S.php │ └── [LEGACY] # Stare klasy (stopniowo deprecated) ├── shop/ ├── admin/factory/ └── front/factory/ ``` ### WAŻNE: Konwencja namespace → katalog (Linux case-sensitive!) - `\Domain\` → `autoload/Domain/` (duże D - nowy katalog) - `\admin\Controllers\` → `autoload/admin/Controllers/` (małe a - istniejący katalog) - NIE używać `\Admin\` (duże A) bo na serwerze Linux katalog to `admin/` (małe a) ## Zasady migracji ### 1. Stopniowość - Przenosimy **jedną funkcję na raz** - Zachowujemy kompatybilność wsteczną - Stare klasy działają jako fasady do nowych ### 2. Dependency Injection zamiast statycznych metod ```php // ❌ STARE - statyczne class Product { public static function getQuantity($id) { global $mdb; return $mdb->get('pp_shop_products', 'quantity', ['id' => $id]); } } // ✅ NOWE - instancje z DI class ProductRepository { private $db; public function __construct($db) { $this->db = $db; } public function getQuantity($id) { return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); } } ``` ### 3. Fasady dla kompatybilności ```php // Stara klasa wywołuje nową namespace shop; class Product { public static function getQuantity($id) { global $mdb; $repo = new \Domain\Product\ProductRepository($mdb); return $repo->getQuantity($id); } } ``` ## Proces migracji funkcji ### Krok 1: Wybór funkcji - Wybierz prostą funkcję statyczną - Sprawdź jej zależności - Przeanalizuj gdzie jest używana ### Krok 2: Stworzenie nowej struktury - Utwórz folder `Domain/{Module}/` - Stwórz odpowiednią klasę (Repository/Service/Entity) - Przenieś logikę ### Krok 3: Znalezienie użyć ```bash grep -r "Product::getQuantity" . ``` ### Krok 4: Aktualizacja wywołań - Opcja A: Bezpośrednie wywołanie nowej klasy - Opcja B: Fasada w starej klasie (zalecane na początek) ### Krok 5: Testy - Napisz test jednostkowy dla nowej funkcji - Sprawdź czy stare wywołania działają ## Status migracji ### ✅ Zmigrowane moduły - **Cache** (częściowo) - ✅ CacheHandler - ma delete/deletePattern - ✅ RedisConnection - singleton - ✅ S::clear_product_cache() - nowa metoda ### 🔄 W trakcie - **Product** - ✅ get_product_quantity() - **ZMIGROWANE** (2025-02-05) 🎉 - Nowa klasa: `Domain\Product\ProductRepository::getQuantity()` - Fasada w: `shop\Product::get_product_quantity()` - Test: `tests/Unit/Domain/Product/ProductRepositoryTest.php` - Testy: ✅ 5/5 przechodzą - Aktualizacja: ver. 0.238 - Użycie DI: ✅ Konstruktor przyjmuje `$db` - ✅ get_product_price() - **ZMIGROWANE** (2026-02-05) 🎉 - Nowa metoda: `Domain\Product\ProductRepository::getPrice()` - Fasada w: `shop\Product::get_product_price()` - Testy: ✅ 4 nowe testy (cena regularna, promocyjna, promo wyższa, nie znaleziono) - Użycie: `front\factory\ShopPromotion` (linia 132) - Aktualizacja: ver. 0.239 - ✅ get_product_name() - **ZMIGROWANE** (2026-02-05) 🎉 - Nowa metoda: `Domain\Product\ProductRepository::getName()` - Fasada w: `shop\Product::get_product_name()` - Testy: ✅ 2 nowe testy (nazwa znaleziona, nie znaleziona) - Użycie: brak aktywnych wywołań (przygotowane na przyszłość) - Aktualizacja: ver. 0.239 - [ ] is_product_on_promotion() - NASTĘPNA 👉 - **Banner** (DEMO pełnej migracji kontrolera) - ✅ BannerRepository - **ZMIGROWANE** (2026-02-05) 🎉 - Nowa klasa: `Domain\Banner\BannerRepository` (find, delete, save, saveTranslations) - Nowy kontroler: `admin\Controllers\BannerController` (DI, instancyjny) - Router: `admin\Site::route()` → sprawdza nowy kontroler → fallback na stary - Testy: ✅ 4 testy (find z tłumaczeniami, not found, delete, save) - Stary kontroler `admin\controls\Banners` działa jako niezależny fallback - Stara factory `admin\factory\Banners` zachowana bez zmian (fallback) - Aktualizacja: ver. 0.239 - **Settings** (migracja kontrolera - krok pośredni) - ✅ SettingsRepository - **ZMIGROWANE** (2026-02-05) 🎉 - Nowa klasa: `Domain\Settings\SettingsRepository` (saveSettings, getSettings) - Krok pośredni: fasada nad `admin\factory\Settings` (docelowo DI z $db) - Nowy kontroler: `admin\Controllers\SettingsController` (DI, instancyjny) - Testy: ✅ 3 testy (instancja, metody) - Stary kontroler `admin\controls\Settings` zachowany jako fallback - Aktualizacja: ver. 0.240 - ✅ CacheRepository - **ZMIGROWANE** (2026-02-05) 🎉 - Nowa klasa: `Domain\Cache\CacheRepository` (clearCache) - Używa `\S::delete_dir()` + `\RedisConnection` - Testy: ✅ 4 testy (z Redis, bez Redis, niedostępny, struktura) - Aktualizacja: ver. 0.240 ### 📋 Do zrobienia - Order - Category - ShopAttribute - ShopProduct (factory) ## Testowanie ### Framework: PHPUnit Instalacja: ```bash composer require --dev phpunit/phpunit ``` ### Struktura testów ``` tests/ ├── Unit/ │ ├── Domain/ │ │ ├── Product/ProductRepositoryTest.php # 11 testów │ │ ├── Banner/BannerRepositoryTest.php # 4 testy │ │ ├── Settings/SettingsRepositoryTest.php # 3 testy │ │ └── Cache/CacheRepositoryTest.php # 4 testy │ └── admin/ │ └── Controllers/SettingsControllerTest.php # 7 testów └── Integration/ ``` **Łącznie: 29 testów, 60 asercji** ### Przykład testu ```php // tests/Unit/Domain/Product/ProductRepositoryTest.php use PHPUnit\Framework\TestCase; use Domain\Product\ProductRepository; class ProductRepositoryTest extends TestCase { public function testGetQuantityReturnsCorrectValue() { // Arrange $mockDb = $this->createMock(\medoo::class); $mockDb->method('get')->willReturn(10); $repo = new ProductRepository($mockDb); // Act $quantity = $repo->getQuantity(123); // Assert $this->assertEquals(10, $quantity); } } ``` ## Zasady kodu ### 1. SOLID Principles - **S**ingle Responsibility - jedna klasa = jedna odpowiedzialność - **O**pen/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje - **L**iskov Substitution - podklasy mogą zastąpić nadklasy - **I**nterface Segregation - wiele małych interfejsów - **D**ependency Inversion - zależności od abstrakcji ### 2. Nazewnictwo - **Entity** - `Product.php` (reprezentuje obiekt domenowy) - **Repository** - `ProductRepository.php` (dostęp do danych) - **Service** - `ProductService.php` (logika biznesowa) - **Controller** - `ProductController.php` (obsługa requestów) ### 3. Type Hinting ```php // ✅ DOBRE public function getQuantity(int $id): ?int { return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); } // ❌ ZŁE public function getQuantity($id) { return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]); } ``` ## Narzędzia pomocnicze ### Autoloader (produkcja) Autoloader w 9 entry pointach obsługuje dwie konwencje: 1. `autoload/{namespace}/class.{ClassName}.php` (legacy) 2. `autoload/{namespace}/{ClassName}.php` (PSR-4, fallback) Entry pointy: `index.php`, `ajax.php`, `api.php`, `cron.php`, `cron-turstmate.php`, `download.php`, `admin/index.php`, `admin/ajax.php`, `cron/cron-xml.php` ### Static Analysis ```bash composer require --dev phpstan/phpstan vendor/bin/phpstan analyse autoload/Domain ``` ## Kolejność refaktoryzacji (priorytet) 1. **Cache** ✅ 2. **Product** (w trakcie) - ✅ getQuantity (ver. 0.238) - ✅ getPrice (ver. 0.239) - ✅ getName (ver. 0.239) - [ ] is_product_on_promotion - NASTĘPNA 👉 - [ ] getFromCache - [ ] getProductImg 3. **Banner** ✅ (pełna migracja kontrolera, ver. 0.239) 4. **Settings** ✅ (migracja kontrolera - krok pośredni, ver. 0.240) 5. **Order** 5. **Category** 6. **ShopAttribute** --- *Rozpoczęto: 2025-02-05* *Ostatnia aktualizacja: 2026-02-05*