Files
shopPRO/REFACTORING_PLAN.md

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
    • get_product_price() - NASTĘPNA 👉
    • get_product_name()

📋 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)

  1. Cache (już w trakcie)
  2. Product (rozpoczynamy)
    • getQuantity
    • getPrice
    • getName
    • getFromCache
  3. ProductRepository (dostęp do bazy)
  4. ProductService (logika biznesowa)
  5. Order
  6. Category

Rozpoczęto: 2025-02-05 Ostatnia aktualizacja: 2025-02-05