Files
shopPRO/docs/FRONTEND_REFACTORING_PLAN.md

32 KiB
Raw Blame History

Plan refaktoryzacji frontendu shopPRO

Kontekst

Panel administratora (33 moduły) został w pełni zmigrowany na architekturę Domain + DI + Controllers (ver. 0.2370.277). Teraz kolej na frontend: klasy front/, shop/, cms/, klasy utility z autoload/ oraz szablony templates/.

Cel: przenieść logikę biznesową z warstwy frontendowej do warstwy domenowej (Domain/), zachowując kompatybilność wsteczną przez fasady. Zastosować te same wzorce co w adminie (DI, repozytoria/serwisy, testy PHPUnit).


Inwentaryzacja — co istnieje

front/controls/ (8 klas — handlery requestów)

Klasa Status Logika biznesowa
Site ZMIGROWANY do front\App — usunięty route(), checkUrlParams(), title(), pageTitle(), getControllerFactories()
ShopBasket ZMIGROWANY do front\Controllers\ShopBasketController Operacje koszyka, add/remove/quantity, checkout
ShopClient ZMIGROWANY do front\Controllers\ShopClientController Logowanie, rejestracja, odzyskiwanie hasla, adresy, zamowienia
ShopOrder ZMIGROWANY do front\Controllers\ShopOrderController Webhooki płatności + order details
ShopProduct ZMIGROWANY — usunięty logika w ProductRepository + szablony
ShopProducer ZMIGROWANY do front\Controllers\ShopProducerController list(), products()
ShopCoupon ZMIGROWANY do front\Controllers\ShopCouponController use_coupon(), delete_coupon()
Newsletter ZMIGROWANY do front\Controllers\NewsletterController signin(), confirm(), unsubscribe()

front/factory/ (20 klas — USUNIĘTY w ver. 0.292, wszystkie zmigrowane do Domain)

Klasa Status Priorytet migracji
ShopProduct ZMIGROWANA do ProductRepository — usunięta
ShopOrder ZMIGROWANA do OrderRepository — usunięta
ShopClient ZMIGROWANA do ClientRepository + ShopClientController — usunięta
ShopCategory ZMIGROWANA do CategoryRepository — usunięta
Articles ZMIGROWANA do ArticleRepository — usunięta
ShopPromotion ZMIGROWANA do PromotionRepository — usunięta
ShopBasket ZMIGROWANA do Domain\Basket\BasketCalculator — usunięta
ShopTransport ZMIGROWANA do TransportRepository — usunięta
ShopPaymentMethod ZMIGROWANA do PaymentMethodRepository — usunięta
ShopStatuses ZMIGROWANA do ShopStatusRepository — usunięta
Scontainers ZMIGROWANA (Domain) — usunięta
Newsletter ZMIGROWANA (Domain) — usunięta
Settings ZMIGROWANA do SettingsRepository — usunięta
Languages USUNIĘTA — przepięta na Domain
Layouts USUNIETA — przepieta na Domain
Banners USUNIETA — przepieta na Domain
Menu USUNIETA — przepieta na Domain
Pages USUNIETA — przepieta na Domain
ShopAttribute ZMIGROWANA (Domain) — usunięta
ShopCoupon ZMIGROWANA do CouponRepository — usunięta

front/view/ (12 klas — renderowanie)

Klasa Status
Site ZMIGROWANY do front\LayoutEngine — usunięty
ShopCategory PRZENIESIONA do front\Views\ShopCategory
Articles Czyste VIEW
Scontainers PRZENIESIONA do front\Views\Scontainers
Menu PRZENIESIONA do front\Views\Menu
Banners PRZENIESIONA do front\Views\Banners
Languages, Newsletter PRZENIESIONE do front\Views\ (nowy namespace)
ShopClient PRZENIESIONA do front\Views\ShopClient
ShopOrder ZMIGROWANA do ShopOrderController — usunięta
ShopPaymentMethod USUNIĘTA (pusta klasa)
ShopTransport USUNIĘTA (pusta klasa)

shop/ (14 klas — encje biznesowe)

Klasa Linii Status Priorytet
Product ~950 KRYTYCZNY — pricing, atrybuty, search, cache, permutacje KRYTYCZNY
Order ~500+ KRYTYCZNY — statusy, Apilo sync, emaile, webhooki KRYTYCZNY
Promotion ~200 WYSOKI — matching aktywnych promocji + delegacja do factory WYSOKI
Basket ~80 ŚREDNI — walidacja stanów magazynowych ŚREDNI
Category ~60 NISKI — proste zapytania, pusty konstruktor NISKI
Search ~80 NISKI — wrapper na szablony NISKI
Coupon ~60 NISKI — niekompletne metody NISKI
Transport ~30 NISKI — transport_list() NISKI
PaymentMethod USUNIETA — callery na PaymentMethodRepository
Producer USUNIETA (callery na ProducerRepository)
ProductSet ZMIGROWANA (fasada do Domain)
ProductAttribute ~100 OK — dobry caching
ProductCustomField ~50 OK — Redis caching
Shop ~30 OK — utility do cen

autoload/ root (klasy utility)

Klasa Linii Status
S ~960 PRZENIESIONA do Shared\Helpers\Helpers — 12 metod usunieto, bug fix
Tpl ~90 PRZENIESIONA do Shared\Tpl\Tpl — DRY render(), bug fix branch 3, ~135 plikow przepietych
CacheHandler ~50 ZMIGROWANY do Shared\Cache\CacheHandler — wrappery usuniete
RedisConnection ~40 ZMIGROWANY do Shared\Cache\RedisConnection — wrappery usuniete
Email ~100 OK — PHPMailer wrapper (drobne poprawki)
Log ~20 OK — audit logging
DbModel USUNIETA — logika wbudowana w shop\Promotion
Cache ~50 USUNIETA — zastapiona CacheHandler (Redis) w ver. 0.282
Html ~80 OK — form helpers
Image ~100 OK — GD wrapper
Mobile_Detect USUNIETA — przestarzala detekcja UA, zastapiona responsive design

cms/ (1 klasa)

Klasa Status
Layout USUNIETA — zastapiona przez $layoutsRepo->find()

templates/ (75 plików w 15 modułach)

articles(8), banner(2), controls(1), menu(4), newsletter(2), scontainers(1), shop-basket(9), shop-category(6), shop-client(8), shop-coupon(1), shop-order(3), shop-producer(3), shop-product(12), shop-search(3), site(11)

Entry points

  • index.php — główny frontend (autoload, sesja, DB, routing, DOM post-processing)
  • ajax.php — AJAX handler (koszyk, transport, kontakt)
  • api.php — REST API (Ekomi CSV export)
  • download.php — pobieranie plików
  • cron-turstmate.php — TrustMate integracja

Znane bugi do naprawy podczas refaktoringu

  1. KRYTYCZNY front\factory\ShopClient::login() — hardcoded password bypass 'Legia1916' NAPRAWIONEClientRepository::authenticate() bez bypass
  2. front\factory\Settings::get_single_settings_value() — ignoruje $param, zawsze zwraca firm_name NAPRAWIONE — klasa usunięta, SettingsRepository::getSingleValue() z poprawnym $param
  3. front\factory\Newsletter::newsletter_unsubscribe() — błędna składnia SQL w delete NAPRAWIONENewsletterRepository::unsubscribe() z poprawną składnią medoo delete()
  4. cms\Layout::__get() — referuje nieistniejące $this->data NAPRAWIONE — klasa usunięta, zastąpiona przez $layoutsRepo->find()
  5. shop\Search — typo w use: shop\Produt (brak 'c')

Kolejność migracji (graf zależności)

Settings, Languages (liście — brak zależności)
    ↓
Banners, Menu, Pages, Articles, Layouts (zależą od Languages)
    ↓
Category (zależy od Languages)
    ↓
Promotion (zależy od Category — rozbicie circular dep)
    ↓
Product Frontend (zależy od Category, Promotion, Languages)
    ↓
Client/Auth (standalone + security fix)
    ↓
Transport, Payment, Coupon (frontend serwisy)
    ↓
Basket (zależy od Product, Promotion, Transport, Coupon)
    ↓
Order Creation (zależy od Basket, Product, Transport, Payment)
    ↓
Payment Webhooks (zależy od Order)
    ↓
shop\Order instance + Apilo (zależy od Order Operations)
    ↓
shop\Product instance + cache (zależy od ProductFrontendService)
    ↓
Frontend App + Controllers (nowa warstwa DI)
    ↓
Site Layout Engine (zależy od wszystkiego)
    ↓
Entry Point Unification (index.php, ajax.php)
    ↓
Legacy Cleanup

Etapy migracji

Etap: Settings + Languages — ZREALIZOWANY

Cel: Dodac metody frontendowe (z cache Redis) do istniejacych repozytoriow Domain.

DODANE METODY (do istniejacych klas):

  • Domain/Settings/SettingsRepositoryallSettings($skipCache), getSingleValue($param) (FIX: uzywa poprawnie $param)
  • Domain/Languages/LanguagesRepositorydefaultLanguage(), activeLanguages(), translations($lang)
  • Testy: dopisane do SettingsRepositoryTest (6 testow), LanguagesRepositoryTest (7 testow)

ZMIANA:

  • front/factory/Settings → fasada delegujaca do SettingsRepository
  • front/factory/Languages → fasada delegujaca do LanguagesRepository
  • tests/bootstrap.php — uzupelniony stub CacheHandler o get()/set()/exists()

BUG FIX: get_single_settings_value() — zmiana ['param' => 'firm_name'] na ['param' => $param]


Etap: Newsletter Frontend — ZREALIZOWANY

Cel: Przeniesienie logiki frontendowej z front\factory\Newsletter do Domain\Newsletter\NewsletterRepository. Migracja view do nowego namespace front\Views.

DODANE METODY (do istniejącej klasy NewsletterRepository):

  • unsubscribe(string $hash): bool — FIX: poprawna składnia medoo delete() (2 args zamiast 3)
  • confirmSubscription(string $hash): bool
  • getHashByEmail(string $email): ?string
  • removeByEmail(string $email): bool
  • signup(string $email, string $serverName, bool $ssl, array $settings): bool
  • sendQueued(int $limit, string $serverName, bool $ssl, string $unsubscribeLabel): bool
  • Konstruktor rozszerzony o opcjonalne: ArticleRepository, NewsletterPreviewRenderer (lazy-init)
  • Testy: 10 nowych testów w NewsletterRepositoryTest

ZMIANA:

  • front/factory/Newsletter → USUNIĘTA (logika przeniesiona do NewsletterRepository)
  • front/view/Newsletter → USUNIĘTA, zastąpiona przez front/Views/Newsletter (nowy namespace, bez class. prefix)
  • front/controls/Newsletter → thin wrapper na NewsletterRepository
  • front/view/Site::show()\front\Views\Newsletter::render()
  • index.php$newsletterRepo->sendQueued()
  • front/factory/ShopClient (4 miejsca) → NewsletterRepository::templateByName()
  • tests/bootstrap.php — dodane stuby: S::email_check(), S::get_session(), S::set_session()

BUG FIX: newsletter_unsubscribe() — medoo delete() wywoływane z 3 argumentami zamiast 2


Etap: Category Frontend Service — ZREALIZOWANY

Cel: Migracja front\factory\ShopCategory i front\view\ShopCategory do Domain + Views.

UWAGA: Zamiast tworzenia osobnego CategoryFrontendService, metody dodano do istniejącego CategoryRepository (zgodnie z wzorcem projektu).

DODANE METODY (do istniejącej klasy CategoryRepository):

  • getCategorySort(int $categoryId, string $langId): int — z Redis cache
  • categoryName(int $categoryId, string $langId): string — z Redis cache
  • categoryUrl(int $categoryId, string $langId): string — z Redis cache
  • frontCategoryDetails(int $categoryId, string $langId): ?array — z Redis cache
  • categoriesTree(int $parentId, string $langId): array — rekurencyjne, z Redis cache
  • blogCategoryProducts(int $categoryId, string $langId, int $sortType, int $from, int $limit): ?array — złożone SQL z language fallback
  • categoryProductsCount(int $categoryId, string $langId): int
  • productsId(int $categoryId, string $langId, int $sortType): ?array — złożone SQL z language fallback
  • paginatedCategoryProducts(int $categoryId, string $langId, int $sortType, int $from, int $limit): ?array
  • Stałe: SORT_ORDER_SQL, PRODUCTS_PER_PAGE, LANGUAGE_FALLBACK_NAME_SQL
  • Testy: +17 w CategoryRepositoryTest

NOWE:

  • front\Views\ShopCategory — czysty VIEW (categoryDescription(), categoryView(), categories())
  • BUG FIX: categoryView()category_products_count() wywoływane z tablicą zamiast ID

ZMIANA:

  • front/factory/ShopCategory → USUNIETA (logika przeniesiona do CategoryRepository)
  • front/view/ShopCategory → USUNIETA (zastąpiona przez front\Views\ShopCategory)
  • index.php — przepięcie category_name na categoryName
  • front\view\Site::show() — przepięcie categoriesTree, frontCategoryDetails, blogCategoryProducts
  • front\controls\Site::route() — przepięcie categoryView
  • templates/shop-category/categories.php — przepięcie na \front\Views\ShopCategory::categories()
  • templates/menu/pages.php — przepięcie category_url na categoryUrl
  • front\controls\ShopProduct — przepięcie products_id + get_category_sort

Testy: 501 OK, 1562 asercji


Etap: Articles Frontend — ZREALIZOWANY

Cel: Migracja front\factory\Articles, front\view\Articles i statycznych metod class.Article do Domain + Views.

DODANE METODY (do istniejącej klasy ArticleRepository):

  • articleDetailsFrontend(int $articleId, string $langId): ?array — z copy_from fallback + Redis cache
  • articlesIds(int $pageId, string $langId, int $limit, int $sortType, int $from): ?array — złożone SQL z language fallback + sortowanie + LIMIT + Redis cache
  • pageArticlesCount(int $pageId, string $langId): int — COUNT z Redis cache
  • pageArticles(array $page, string $langId, int $bs): array — paginacja
  • news(int $pageId, int $limit, string $langId): ?array — inline sort_type query (eliminacja zależności od front\factory\Pages)
  • articleNoindex(int $articleId, string $langId): bool — jawny $langId zamiast global $lang
  • topArticles(int $pageId, int $limit, string $langId): ?array — ORDER BY views DESC + Redis cache
  • newsListArticles(int $pageId, int $limit, string $langId): ?array — ORDER BY date_add DESC + CacheHandler (Redis) zamiast legacy \Cache

NOWE:

  • front\Views\Articles — czysty VIEW + utility:
    • Renderowanie: fullArticle(), miniatureArticlesList(), entryArticlesList(), fullArticlesList(), news(), newsList()
    • Utility: generateTableOfContents(), processHeaders(), generateHeadersIds(), getImage()

ZMIANA:

  • front\factory\Articles → fasada (10 metod delegujących do repo + Views)
  • front\view\Articles → fasada (5 metod delegujących do repo + Views)
  • class.Article → USUNIĘTA (encja + metody statyczne przeniesione do ArticleRepository + front\Views\Articles)
  • front\view\Site::show() → 5 sekcji przepiętych na repo + Views
  • front\controls\Site::route() → single article + page_type switch przepięte na repo + Views
  • 5 szablonów templates/articles/*\front\Views\Articles::
  • tests/bootstrap.php — dodany stub S::is_array_fix()
  • Testy: 13 nowych w ArticleRepositoryTest (450 OK, 1431 asercji)

Etap: Banners Frontend — ZREALIZOWANY

Cel: Migracja front\factory\Banners i front\view\Banners do Domain + Views.

DODANE METODY (do istniejącej klasy BannerRepository):

  • banners(string $langId): ?array — aktywne banery (home_page=0), filtrowanie dat, Redis cache, plaski format languages
  • mainBanner(string $langId): ?array — baner glowny (home_page=1), filtrowanie dat, Redis cache, plaski format languages

NOWE:

  • front\Views\Banners — czysty VIEW (banners(), mainBanner())

ZMIANA:

  • front\factory\Banners → USUNIETA (logika przeniesiona do BannerRepository)
  • front\view\Banners → USUNIETA (zastapiona przez front\Views\Banners)
  • front\view\Site::show() — przepiecie na $bannerRepo->banners() / $bannerRepo->mainBanner() + \front\Views\Banners::
  • Testy: 4 nowe w BannerRepositoryTest (454 OK, 1449 asercji)

Etap: Menu, Pages, Layouts Frontend Services — ZREALIZOWANY

Cel: Migracja pozostałych fabryk "liściowych".

UWAGA: Zamiast tworzenia osobnych FrontendService, metody dodano do istniejących repozytoriów Domain (zgodnie z wzorcem projektu).

DODANE METODY (do istniejących klas):

  • Domain/Layouts/LayoutsRepositorycategoryDefaultLayoutId(), getDefaultLayout(), getProductLayout(), getArticleLayout(), getCategoryLayout(), getActiveLayout() (Redis cache, 3-level fallback)
  • Domain/Pages/PagesRepositoryfrontPageDetails(), frontPageSort(), frontMainPageId(), frontLangUrl(), frontMenuDetails(), frontMenuPages() (Redis cache, rekurencja)
  • Testy: +8 w LayoutsRepositoryTest, +8 w PagesRepositoryTest

NOWE:

  • front\Views\Menu — czysty VIEW (pages(), menu())

ZMIANA:

  • front/factory/Layouts → USUNIETA (logika w LayoutsRepository)
  • front/factory/Menu → USUNIETA (logika w PagesRepository)
  • front/factory/Pages → USUNIETA (logika w PagesRepository)
  • front/view/Menu → USUNIETA (zastapiona przez front\Views\Menu)
  • templates/menu/submenu.php → USUNIETA (martwy kod)
  • front\view\Site::show() — przepiecie na $layoutsRepo + $pagesRepo
  • front\controls\Site::check_url_params() — przepiecie na $pagesRepo->frontPageDetails()
  • index.php — przepiecie na $pagesRepo->frontPageDetails()
  • Shared\Helpers\Helpers::htacces() — optymalizacja 3→1 wywolan
  • Szablony templates/menu/* — przepiecie na \front\Views\Menu::
  • templates/site/languages.php — przepiecie na $pagesRepo->frontLangUrl()

BUG FIX: frontPageDetails() — null $lang_id przy wczesnym check_url_params() (usuniety string type hint + cast + ?? '' na call site)

Testy: 470 OK, 1484 asercji


Etap: Promotion Engine (rozbicie circular dependency)

Cel: Przeniesienie silnika promocji do Domain. Rozbicie cyklicznej zależności shop\Promotion ↔ front\factory\ShopPromotion.

Obecna zależność cykliczna:

shop\Promotion::find_promotion() → front\factory\ShopPromotion::promotion_type_XX()
front\factory\ShopPromotion::promotion_type_XX() → shop\Product::is_product_on_promotion()

Rozwiązanie: Wszystko w jednym serwisie PromotionFrontendService.

NOWE:

  • Domain/Promotion/PromotionFrontendService.php:
    • getActivePromotions() (z shop\Promotion)
    • applyPromotions(array $basket) (z shop\Promotion::find_promotion())
    • private applyType01..05() (z front\factory\ShopPromotion)
    • private isProductOnPromotion() (z shop\Product — proste zapytanie)
  • Testy: PromotionFrontendServiceTest (7 typów promocji + brak aktywnych)

ZMIANA:

  • shop/Promotion → fasada do PromotionFrontendService::applyPromotions()
  • front/factory/ShopPromotion → fasada

Etap: Product Frontend Service

Cel: Migracja front\factory\ShopProduct i statycznych metod shop\Product do Domain.

NOWE:

  • Domain/Product/ProductFrontendService.php:
    • Z front\factory\ShopProduct: productDetails() (~330 linii z Redis), promotedProducts(), topProducts(), newProducts(), productUrl(), getMinimalPrice(), isProductActive(), getProductSku(), getProductEan(), productImage(), productWp(), randomProducts(), warehouseMessageZero(), warehouseMessageNonzero()
    • Z shop\Product (statyczne): isProductOnPromotion(), getProductImg(), getProductUrl(), addVisit(), productSetsForBasket(), searchProductsByName(), searchProductByNameAjax()
  • Testy: ProductFrontendServiceTest

ZMIANA:

  • front/factory/ShopProduct → fasada
  • shop/Product statyczne metody → fasady do ProductFrontendService

UWAGA: Konstruktor shop\Product, getFromCache(), calculate_basket_product_price() — zostają na razie (instancyjne, używane w szablonach). Migracja w etapie "Product Instance + Cache".


Etap: Client Authentication (Security Fix) — ZREALIZOWANY

Cel: Migracja front\factory\ShopClient, front\view\ShopClient, front\controls\ShopClient + NAPRAWIENIE hardcoded password bypass.

UWAGA: Zamiast tworzenia osobnego ClientFrontendService, metody dodano do istniejącego ClientRepository (zgodnie z wzorcem projektu). Kontroler utworzony jako front\Controllers\ShopClientController z DI.

DODANE METODY (do istniejącej klasy ClientRepository):

  • Simple CRUD: clientDetails(), clientEmail(), clientAddresses(), addressDetails(), addressDelete(), addressSave(int $clientId, ?int $addressId, array $data), markAddressAsCurrent()
  • clientOrders() — zachowuje zależność od \front\factory\ShopOrder::order_details()
  • authenticate(string $email, string $password)BEZ bypassa 'Legia1916', zwraca ['status' => 'ok'|'error'|'inactive', ...]
  • createClient() — zwraca ['id' => ..., 'hash' => ...] lub null
  • confirmRegistration() — zwraca email lub null
  • generateNewPassword() — zwraca ['email' => ..., 'password' => ...] lub null
  • initiatePasswordRecovery() — zwraca hash lub null
  • Testy: +36 w ClientRepositoryTest (guard clauses, authenticate 5 scenariuszy, createClient, confirmRegistration, generateNewPassword, initiatePasswordRecovery, address CRUD, markAddressAsCurrent)

NOWE:

  • front\Views\ShopClient — czysty VIEW (8 metod camelCase: addressEdit, clientAddresses, clientMenu, clientOrders, recoverPassword, miniLogin, loginForm, registerForm)
  • front\Controllers\ShopClientController — instancyjny kontroler z DI (ClientRepository przez konstruktor)
    • 15 metod publicznych (camelCase)
    • Prywatny helper buildEmailBody(string $templateName, array $replacements = []) — deduplikuje 4× powtórzony wzorzec budowania emaili z newslettera
    • authenticate() zwraca dane → kontroler obsługuje sesję/flash/redirect (separation of concerns)
    • addressSave() przyjmuje array $data zamiast 6 parametrów

ZMIANA:

  • front/factory/ShopClient → USUNIETA (logika przeniesiona do ClientRepository)
  • front/view/ShopClient → USUNIETA (zastąpiona przez front\Views\ShopClient)
  • front/controls/ShopClient → USUNIETA (zastąpiona przez front\Controllers\ShopClientController)
  • front\controls\Site::getControllerFactories() — zarejestrowany 'ShopClient'
  • front\factory\ShopOrder — przepięcie client_email na ClientRepository::clientEmail()
  • front\Controllers\ShopBasketController — przepięcie client_addresses na ClientRepository::clientAddresses()
  • front\view\Site — przepięcie mini_login na \front\Views\ShopClient::miniLogin()
  • 3 szablony shop-client/* — przepięcie client_menu na \front\Views\ShopClient::clientMenu()
  • templates/shop-basket/summary-view.php — przepięcie login_form na \front\Views\ShopClient::loginForm()

SECURITY FIX: Hardcoded password bypass 'Legia1916' usunięty — authenticate() sprawdza wyłącznie md5(register_date + password)

Testy: 537 OK, 1648 asercji


Etap: Transport, Payment, Coupon Frontend Services

Cel: Frontend serwisy dla transportu, płatności i kuponów.

NOWE:

  • Domain/Transport/TransportFrontendService.phptransportMethods() (filtrowanie WP, free delivery), transportCost(), transportDetails()
  • Domain/PaymentMethod/PaymentMethodFrontendService.phpactivePaymentMethods(), paymentMethodsByTransport(), paymentMethodDetails()
  • Domain/Coupon/CouponFrontendService.phpvalidateCoupon(), applyCoupon(), markCouponUsed()
  • Testy: 3 pliki testowe

ZMIANA:

  • front/factory/ShopTransport → fasada
  • front/factory/ShopCoupon → fasada
  • shop/Coupon → fasada
  • shop/Transport → fasada

Etap: Basket Service

Cel: Migracja logiki koszyka do Domain\Basket\BasketService.

NOWE:

  • Domain/Basket/BasketService.php:
    • addProduct(), removeProduct(), changeQuantity(), increaseQuantity(), decreaseQuantity()
    • summaryPrice(), countProducts(), countProductsText()
    • validateStock() (z shop\Basket::check_product_quantity_in_stock())
    • calculateProductPrice() (z shop\Product::calculate_basket_product_price() ~112 linii)
  • Testy: BasketServiceTest (pricing z promocjami/kuponami, walidacja stanów)

ZMIANA:

  • front/factory/ShopBasket → fasada
  • front/controls/ShopBasket — wewnętrznie używa BasketService
  • shop/Basket → fasada
  • shop/Product::calculate_basket_product_price() → fasada

Etap: shop\Product Instance + Cache

Cel: Refaktoring konstruktora shop\Product i getFromCache().

NOWE:

  • Domain/Product/ProductDataLoader.php:
    • loadFull(int $productId, ?string $langId, ?string $permutationHash) — ładowanie pełnych danych produktu
    • loadCached() — Redis cache wrapper
  • Testy: ProductDataLoaderTest

ZMIANA:

  • shop/Product — konstruktor i getFromCache() delegują do ProductDataLoader
  • ArrayAccess interface zachowany (szablony dalej używają $product->property)

Etap: Order Creation Frontend Service — ZREALIZOWANY (ver. 0.290)

Cel: Migracja front\factory\ShopOrder::basket_save() (~180 linii).

ZREALIZOWANE: (wg wytycznej "NIE tworzymy osobnych FrontendService/AdminService" — metody dodane do istniejącego OrderRepository)

  • Domain/Order/OrderRepository.php — dodane metody frontendowe:
    • createFromBasket() — tworzenie zamówienia z koszyka (21 parametrów, pełna logika basket_save)
    • generateOrderNumber() — format YYYY/MM/NNN
    • orderDetailsFrontend(), findIdByHash(), findHashById()
  • front/Controllers/ShopOrderController.php — kontroler z DI (OrderRepository)
  • Testy: OrderRepositoryTest (9 nowych), ShopOrderControllerTest (3 nowe)

USUNIĘTE:

  • front/factory/class.ShopOrder.php
  • front/controls/class.ShopOrder.php
  • front/view/class.ShopOrder.php

CALLERY ZAKTUALIZOWANE:

  • ShopBasketController — DI OrderRepository, zmiana basket_save/order_hash
  • ClientRepository::clientOrders() — OrderRepository::orderDetailsFrontend()
  • shop\Order::order_resend_confirmation_email() — OrderRepository::orderDetailsFrontend()
  • cron-turstmate.php — OrderRepository::orderDetailsFrontend()

Etap: Payment Webhook Service — ZREALIZOWANY (ver. 0.290)

Cel: Wyodrębnienie webhooków płatności z front\controls\ShopOrder.

ZREALIZOWANE: (webhooki przeniesione do front\Controllers\ShopOrderController — nadal używają \shop\Order do operacji statusów/płatności)

  • ShopOrderController::paymentStatusTpay() — przeniesione 1:1
  • ShopOrderController::paymentStatusPrzelewy24pl() — ujednolicone z tpay (set_as_paid + update_status zamiast ręcznego $mdb->update)
  • ShopOrderController::paymentStatusHotpay() — analogiczna zamiana na \shop\Order metody

UWAGA: \shop\Order nie jest jeszcze zmigrowany — osobny etap (Order Instance + Apilo Service)


Etap: shop\Order Instance + Apilo Service

Cel: Refaktoring shop\Order instancyjnych metod + wyodrębnienie integracji Apilo.

NOWE:

  • Domain/Integrations/ApiloService.php:
    • syncPayment(), syncStatus(), processQueue()
    • private queueSync(), private loadQueue(), private saveQueue()
  • Domain/Order/OrderOperationsService.php:
    • setAsPaid(), setAsUnpaid(), updateStatus(), sendStatusChangeEmail(), resendConfirmationEmail()
  • Testy: ApiloServiceTest, OrderOperationsServiceTest

ZMIANA:

  • shop/Order instancyjne metody → fasady do OrderOperationsService
  • Konstruktor i ArrayAccess bez zmian

Etap: Frontend App + Controllers (DI layer)

Cel: Stworzenie front\App (wzorowanego na admin\App) z mapą kontrolerów i DI.

NOWE:

  • autoload/front/App.phprender(), handleAjax(), getControllerFactories() (DI wiring)
  • autoload/front/Controllers/ShopBasketController.php — DI z BasketService, ProductFrontendService
  • autoload/front/Controllers/ShopOrderController.php — DI z OrderFrontendService, PaymentWebhookService
  • autoload/front/Controllers/ShopClientController.php — DI z ClientFrontendService
  • Testy: 3 pliki testowe kontrolerów

ZMIANA:

  • front/controls/ShopBasket, ShopOrder, ShopClient → delegują do nowych kontrolerów

Etap: Site Layout Engine

Cel: Refaktoring front\view\Site::show() (~600 linii) na testowalny LayoutEngine.

NOWE:

  • autoload/front/View/LayoutEngine.php:
    • resolveLayout(array $page, array $params) — wybór layoutu (product > category > article > page > default)
    • processPatterns(string $html, array $context) — zamiana tagów szablonowych
    • Osobne prywatne metody: replaceCategories(), replaceProducts(), replaceMenus(), replaceLanguageTags(), replaceBanners(), replaceArticles(), replaceContainers(), replaceMetaTags(), etc.
  • Testy: LayoutEngineTest

ZMIANA:

  • front/view/Site::show() → thin wrapper na LayoutEngine

Etap: Entry Point Unification

Cel: Ujednolicenie bootstrapu index.php i ajax.php.

NOWE:

  • autoload/front/Bootstrap.phpinit() (autoloader, config, sesja, DB, lang/settings)
  • autoload/front/PostProcessor.php — ekstrakcja DOM manipulacji z index.php (GTM, Facebook Pixel, WebP, lazy loading)

ZMIANA:

  • index.php → uproszczony: Bootstrap::init()App::render()PostProcessor::process()
  • ajax.php → uproszczony: Bootstrap::init()App::handleAjax()

Etap: Legacy Cleanup

Cel: Finalne porządki.

USUNIĘCIE:

  • Martwy kod eval() dla [PHP] bloków (jeśli nieużywany)
  • class.Cache.php (legacy file-based) ZREALIZOWANE w ver. 0.282
  • Pusta klasa front\view\ShopTransport

ZMIANA:

  • Dodanie @deprecated do wszystkich metod-fasad w front/factory/, front/controls/, shop/
  • PHPDoc do wszystkich nowych klas Domain z @since
  • Aktualizacja tests/bootstrap.php
  • BUG FIX: shop\Search — typo shop\Produtshop\Product
  • BUG FIX: front\factory\Newsletter::newsletter_unsubscribe() — poprawka SQL ZREALIZOWANE w etapie Newsletter Frontend

Podsumowanie

Etap Zakres Priorytet Nowe klasy Domain Testy
Settings + Languages Fundamenty FUNDAMENT 2 serwisy 2
Category Frontend Kategorie ZREALIZOWANY
Banners/Menu/Pages/Articles/Layouts Treści ZREALIZOWANY
Promotion Engine Promocje KRYTYCZNY 1 serwis 1
Product Frontend Produkty KRYTYCZNY 1 serwis 1
Client/Auth (security fix) Klienci ZREALIZOWANY
Transport/Payment/Coupon Dostawa/Płatności WYSOKI 3 serwisy 3
Basket Service Koszyk WYSOKI 1 serwis 1
Product Instance + Cache Produkt cache ŚREDNI 1 loader 1
Order Creation Zamówienia WYSOKI 1 serwis 1
Payment Webhooks Webhooki WYSOKI 1 serwis 1
Order Instance + Apilo Zamówienie + Apilo ŚREDNI 2 serwisy 2
Frontend App + Controllers DI layer WYSOKI App + 3 kontrolery 3
Layout Engine Silnik layoutu ŚREDNI 1 engine 1
Entry Point Unification Entry points ŚREDNI Bootstrap + PostProcessor 1
Legacy Cleanup Porządki NISKI

Łącznie: 16 etapów, ~24 nowe klasy Domain, ~25 plików testowych

Wzorce do przestrzegania (z migracji admin)

  • Konstruktor DI z $db (medoo)
  • CacheHandler (Redis) dla cachingu — unifikacja (usunięcie starego Cache)
  • Fasady w shop\* i front\factory\* dla kompatybilności wstecznej
  • Testy PHPUnit z mockami medoo
  • Namespace \Domain\*autoload/Domain/*/
  • Namespace \front\Controllers\autoload/front/Controllers/
  • Klasy Domain sa wspolne dla admin i frontendu — NIE tworzymy osobnych FrontendService/AdminService. Metody frontendowe (z cache Redis) dodajemy do istniejacych repozytoriow/serwisow Domain. Klasy sa ladowane lazy (instancja tworzona dopiero przy wywolaniu), wiec nie wplywaja na wydajnosc.

Nazewnictwo plikow

  • Nowe klasy: NazwaKlasy.php (bez przedrostka class.)
  • Legacy: class.NazwaKlasy.php — zostawiamy do momentu migracji danej klasy
  • Autoloader obsluguje oba formaty (probuje class.X.php, potem X.php)
  • Nowe katalogi z duzej litery: Views/, Controllers/ (legacy: view/, controls/, factory/)

Statyczne vs instancyjne metody

  • Statyczne — gdy klasa jest bezstanowa (brak konstruktora, brak properties, brak DI). Czyste funkcje: dane wchodzą, wynik wychodzi. Przykład: klasy VIEW (front\Views\Languages::render($data), front\view\Banners::banners($data))
  • Instancyjne — gdy klasa ma zależności do wstrzyknięcia (repozytoria, serwisy) lub trzyma stan. Przykład: kontrolery z DI (ShopProductController z ProductRepository, LanguagesRepository)

Weryfikacja po każdym etapie

  1. composer test (pełny suite PHPUnit)
  2. Manualne sprawdzenie frontendu: strona główna, kategoria, produkt, koszyk, zamówienie
  3. Sprawdzenie AJAX: dodawanie do koszyka, zmiana transportu, kupon
  4. Sprawdzenie webhooków płatności (tPay, Przelewy24, Hotpay)