Files
shopPRO/REFACTORING_PLAN.md
Jacek Pyziak efd93dede3 feat: Migrate article_save and article_delete to Domain Architecture
Move article save/delete logic from monolithic factory to ArticleRepository
with DI-based controller actions, following the established refactoring pattern.

- ArticleRepository: add save() with 9 private helpers, archive() method
- ArticlesController: add save() and delete() actions with DI
- Factory methods delegate to repository (backward compatibility)
- Router: add article_save/article_delete action mappings
- Old controls methods marked @deprecated
- 59 tests, 123 assertions passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 19:52:22 +01:00

12 KiB
Raw Blame History

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

// ❌ 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
    • 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
    • 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 z archive/)
      • Testy: 4 nowe testy repozytorium + 6 testów kontrolera
      • FIX: SQL bug w ajax_products_list_archive() (puste wyszukiwanie + brak archive = 1)
      • Aktualizacja: ver. 0.241
    • 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
  • 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 w admin\Site::$actionMap)
      • Kompatybilność: admin\factory\Articles::article_details() deleguje do nowego repozytorium
      • Legacy cleanup: metody przejęte przez nowe kontrolery oznaczone @deprecated w admin\controls\Articles|Banners|Settings
      • Testy repozytorium rozszerzone o czyszczenie nieprzypisanych plik<69>w/zdj<64><6A>
      • Aktualizacja: ver. 0.243
    • 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 w admin\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
    • ArticleRepository::archive() - ZMIGROWANE (2026-02-06) 🎉
      • Metoda archive() (ustawia status = -1)
      • Zmigrowana akcja: article_delete -> delete (mapowanie w admin\Site::$actionMap)
      • Kompatybilnosc: admin\factory\Articles::articles_set_archive() deleguje do repozytorium
      • Testy: 2 nowe testy archive (success, failure)
      • Aktualizacja: ver. 0.245
  • 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:

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:

  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

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)
    • archive / unarchive (ver. 0.241)
    • 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. ProductArchive (migracja kontrolera + cleanup szablonów, ver. 0.241)
  6. Order
  7. Category
  8. ShopAttribute

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