14 KiB
14 KiB
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, archive, unarchive)
│ │ ├── 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 toadmin/(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
// ❌ 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
// 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ć
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
- Nowa klasa:
- ✅ 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
- Nowa metoda:
- ✅ 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
- Nowa metoda:
- ✅ archive() / unarchive() - ZMIGROWANE (2026-02-06) 🎉
- Nowe metody:
Domain\Product\ProductRepository::archive(),unarchive() - Nowy kontroler:
admin\Controllers\ProductArchiveController(DI, instancyjny) - Szablony:
admin/templates/product_archive/(rename zarchive/) - Testy: ✅ 4 nowe testy repozytorium + 6 testów kontrolera
- FIX: SQL bug w
ajax_products_list_archive()(puste wyszukiwanie + brakarchive = 1) - Aktualizacja: ver. 0.241
- Nowe metody:
- is_product_on_promotion() - NASTĘPNA 👉
- ✅ get_product_quantity() - ZMIGROWANE (2025-02-05) 🎉
-
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\Bannersdziała jako niezależny fallback - Stara factory
admin\factory\Bannerszachowana bez zmian (fallback) - Aktualizacja: ver. 0.239
- Nowa klasa:
- ✅ BannerRepository - ZMIGROWANE (2026-02-05) 🎉
-
Articles (migracja kontrolera - etap edit/details)
- ✅ ArticleRepository::find() - ZMIGROWANE (2026-02-06) 🎉
- Nowa klasa:
Domain\Article\ArticleRepository(find: artykul + relacje) - Nowy kontroler:
admin\Controllers\ArticlesController(DI, instancyjny) - Zmigrowana akcja:
article_edit->edit(mapowanie wadmin\Site::$actionMap) - Kompatybilność:
admin\factory\Articles::article_details()deleguje do nowego repozytorium - Legacy cleanup: metody przejęte przez nowe kontrolery oznaczone
@deprecatedwadmin\controls\Articles|Banners|Settings - Testy repozytorium rozszerzone o czyszczenie nieprzypisanych plik<69>w/zdj<64><6A>
- Aktualizacja: ver. 0.243
- Nowa klasa:
- ✅ ArticleRepository::save() - ZMIGROWANE (2026-02-06) 🎉
- Metoda
save()z prywatnych helperow (buildArticleRow, buildLangRow, saveTranslations, savePages, assignTempFiles, assignTempImages, deleteMarkedFiles, deleteMarkedImages) - Zmigrowana akcja:
article_save->save(mapowanie wadmin\Site::$actionMap) - Kompatybilnosc:
admin\factory\Articles::article_save()deleguje do repozytorium - Testy: 7 nowych testow save (create, update, translations, pages, marked delete)
- Aktualizacja: ver. 0.244
- Metoda
- ✅ ArticleRepository::archive() - ZMIGROWANE (2026-02-06) 🎉
- Metoda
archive()(ustawia status = -1) - Zmigrowana akcja:
article_delete->delete(mapowanie wadmin\Site::$actionMap) - Kompatybilnosc:
admin\factory\Articles::articles_set_archive()deleguje do repozytorium - Testy: 2 nowe testy archive (success, failure)
- Aktualizacja: ver. 0.245
- Metoda
- ✅ ArticlesController::browseList() - ZMIGROWANE (2026-02-07) 🎉
- Nowa metoda kontrolera:
browseList()(DI, instancyjna) - Zmigrowana akcja:
browse_list->browseList(mapowanie wadmin\Site::$actionMap) - Legacy cleanup: usuniety
autoload/admin/controls/class.Articles.php(brak fallback dla modułu Articles) - Testy: 2 nowe testy kontraktu kontrolera (method exists + return type)
- Nowa metoda kontrolera:
- ✅ ArticlesController::galleryOrderSave() - ZMIGROWANE (2026-02-07) 🎉
- Nowa metoda kontrolera:
galleryOrderSave()(AJAX) - Zmigrowana akcja:
gallery_order_save->galleryOrderSave(mapowanie wadmin\Site::$actionMap) - Implementacja: używa
Domain\Article\ArticleRepository::saveGalleryOrder() - Testy: 2 nowe testy kontraktu kontrolera (method exists + return type)
- Nowa metoda kontrolera:
- ✅ Usuniecie legacy kontrolera Articles - ZMIGROWANE (2026-02-07) 🎉
- Usuniety plik:
autoload/admin/controls/class.Articles.php - Wymaganie dla aktualizacji: dodac wpis do
ver_X.XXX_files.txt - Wpis do usuniecia:
F: ../autoload/admin/controls/class.Articles.php
- Usuniety plik:
- ✅ Stabilizacja generatora
.htaccess- ZMIGROWANE (2026-02-07) 🎉- FIX: regula admin ma
QSA(query string dla sortowania/filtrow) - FIX:
\S::htacces()komentujeAddHandler|SetHandler|ForceTypedla zgodnosci z hostingiem - UPDATE:
libraries/htaccess.confzaktualizowany, aby poprawki nie znikaly po regeneracji
- FIX: regula admin ma
- ✅ ArticleRepository::find() - ZMIGROWANE (2026-02-06) 🎉
-
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\Settingszachowany jako fallback - Aktualizacja: ver. 0.240
- Nowa klasa:
- ✅ 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
- Nowa klasa:
- ✅ SettingsRepository - ZMIGROWANE (2026-02-05) 🎉
📋 Do zrobienia
- Order
- Category
- ShopAttribute
- ShopProduct (factory)
Testowanie
Framework: PHPUnit
Instalacja:
composer require --dev phpunit/phpunit
Struktura testów
tests/
├── Unit/
│ ├── Domain/
│ │ ├── Product/ProductRepositoryTest.php # 15 testów
│ │ ├── Banner/BannerRepositoryTest.php # 4 testy
│ │ ├── Settings/SettingsRepositoryTest.php # 3 testy
│ │ └── Cache/CacheRepositoryTest.php # 4 testy
│ └── admin/
│ └── Controllers/
│ ├── SettingsControllerTest.php # 7 testów
│ └── ProductArchiveControllerTest.php # 6 testów
└── Integration/
Łącznie: 48 testów, 91 asercji
Przykład testu
// 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
- Single Responsibility - jedna klasa = jedna odpowiedzialność
- Open/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje
- Liskov Substitution - podklasy mogą zastąpić nadklasy
- Interface Segregation - wiele małych interfejsów
- Dependency 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
// ✅ 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:
autoload/{namespace}/class.{ClassName}.php(legacy)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
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse autoload/Domain
Kolejność refaktoryzacji (priorytet)
- Cache ✅
- Product (w trakcie)
- ✅ getQuantity (ver. 0.238)
- ✅ getPrice (ver. 0.239)
- ✅ getName (ver. 0.239)
- ✅ archive / unarchive (ver. 0.241)
- is_product_on_promotion - NASTĘPNA 👉
- getFromCache
- getProductImg
- Banner ✅ (pełna migracja kontrolera, ver. 0.239)
- Settings ✅ (migracja kontrolera - krok pośredni, ver. 0.240)
- ProductArchive ✅ (migracja kontrolera + cleanup szablonów, ver. 0.241)
- Order
- Category
- ShopAttribute
Rozpoczęto: 2025-02-05 Ostatnia aktualizacja: 2026-02-07