6.3 KiB
6.3 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)
│ ├── Product/
│ │ ├── Product.php # Entity
│ │ ├── ProductRepository.php # Dostęp do bazy
│ │ ├── ProductService.php # Logika biznesowa
│ │ └── ProductCacheService.php # Cache produktu
│ ├── Order/
│ ├── Category/
│ └── ...
│
├── Admin/ # Warstwa administratora
│ ├── Controllers/
│ └── Services/
│
├── Frontend/ # Warstwa użytkownika
│ ├── 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/
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ą (11 asercji)
- Aktualizacja: ver. 0.238
- Użycie DI: ✅ Konstruktor przyjmuje
$db
- Nowa klasa:
- get_product_price() - NASTĘPNA 👉
- get_product_name()
- ✅ get_product_quantity() - ZMIGROWANE (2025-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
│ │ └── ProductServiceTest.php
│ └── Shared/
│ └── Cache/
│ └── CacheHandlerTest.php
└── Integration/
└── Domain/
└── Product/
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
Composer autoloader
Dodaj do composer.json:
{
"autoload": {
"psr-4": {
"Domain\\": "autoload/Domain/",
"Admin\\": "autoload/Admin/",
"Frontend\\": "autoload/Frontend/",
"Shared\\": "autoload/Shared/"
}
}
}
Static Analysis
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse autoload/Domain
Kolejność refaktoryzacji (priorytet)
- Cache (już w trakcie) ✅
- Product (rozpoczynamy)
- getQuantity
- getPrice
- getName
- getFromCache
- ProductRepository (dostęp do bazy)
- ProductService (logika biznesowa)
- Order
- Category
Rozpoczęto: 2025-02-05 Ostatnia aktualizacja: 2025-02-05