Files
drmaterac.pl/.paul/codebase/architecture.md
2026-05-10 21:32:38 +02:00

159 lines
7.8 KiB
Markdown

# Architecture
**Analysis Date:** 2026-05-10
## Pattern Overview
**Overall:** PrestaShop 1.7.8.x MVC e-commerce monolith with hook-based module extensibility and a Symfony admin layer.
**Key Characteristics:**
- Front-office: legacy PrestaShop dispatcher + Smarty templates
- Back-office (admin): Symfony 4-style kernel (`app/AppKernel.php`) at non-standard path `iadmin/`
- Hook-based publish/subscribe extension model — modules attach to named hooks
- Core class overrides via `override/classes/` (monkey-patch layer)
- Multi-domain (`drmaterac.pl` + `lulandia.pl`) served from a single install via `.htaccess` rules
## Layers
**Entry / Routing Layer:**
- Purpose: Route HTTP requests via mod_rewrite to dispatcher / Symfony kernel
- Contains: `.htaccess` (429 lines), `index.php` (front), `iadmin/index.php` (back-office)
- Used by: All HTTP requests
- Key class: `classes/Dispatcher.php` (overridden in `override/classes/Dispatcher.php`)
**Controller Layer:**
- Purpose: Handle request-specific logic (cart, product, checkout, search)
- Contains: `controllers/front/*.php` (CartController, OrderController, ProductController…)
- Base classes: `classes/controller/FrontController.php`, `ModuleFrontController.php`, `AdminController.php`, `ProductListingFrontController.php`
- Overrides: `override/classes/controller/FrontController.php`, `override/classes/controller/ProductListingFrontController.php`
**Model / Domain Layer (ObjectModel):**
- Purpose: ORM-style domain entities (Product, Cart, Order, Customer, Category…)
- Contains: `classes/Product.php`, `classes/Cart.php`, `classes/Order.php`, `classes/Customer.php`, etc.
- Base: `classes/ObjectModel.php`
- Overrides: 31 files in `override/classes/` (e.g. `override/classes/Product.php` adds Google Shopping fields; `override/classes/Cart.php` extends `getOrderTotal()` for ets_promotion pricing)
**Module Layer (extensibility):**
- Purpose: Optional features attached via hooks; first-class extension mechanism
- Contains: `modules/*/` (50+ installed modules, ~138 entries on disk)
- Custom in this project: `modules/crosssellpro/` (Cross Sell PRO carousel), `modules/caraty/`
- Active third-party: `modules/paynow/`, `modules/santandercredit/`, `modules/empikmarketplace/`, `modules/dpdpoland/`, `modules/pdgoogleanalytycs4pro/`, `modules/fbpixel/`, `modules/ekomiratingsandreviews/`, `modules/ceneo_trustedreviews/`, etc.
**Theme / Presentation Layer:**
- Purpose: Page templates and visual assets
- Contains: `themes/leo_gstore/` (active child theme, Leo Gstore v1.4)
- Templates: `themes/leo_gstore/templates/*.tpl` (layout, cart, checkout, product…)
- Per-module overrides: `themes/leo_gstore/modules/<module>/views/templates/hook/*.tpl`
- Assets: `themes/leo_gstore/assets/cache/` (bundled JS/CSS)
**Cross-cutting / Hook System:**
- `classes/Hook.php` + `override/classes/Hook.php` (PageCache module integration)
- Hooks dispatch from controllers/templates → fire each registered module's `hook<Name>()` method
- Registration table: `ps_hook_module`
## Data Flow
**HTTP Request lifecycle (front-office):**
1. Apache receives request → `.htaccess` rewrite rule (line ~353) routes non-physical URLs to `index.php`
2. `Dispatcher::getInstance()->dispatch()` resolves URL to a controller (e.g. `CartController`)
3. Controller `init()` / `initContent()` runs; sets up `Context` (cart, customer, shop, language)
4. Controller dispatches display hooks (`displayHeader`, `displayShoppingCartFooter`, `actionFrontControllerSetMedia`, etc.)
5. Each subscribing module's `hook<Name>()` method runs and may render Smarty partials
6. Smarty renders the theme template (`themes/leo_gstore/templates/cart.tpl`) injecting hook output
7. Response (HTML + CSS + JS) returned to client
**Admin lifecycle:**
- `iadmin/index.php` boots `app/AppKernel.php` (Symfony) — `iadmin/` is the renamed admin folder (security-by-obscurity)
- Symfony routes to controllers; falls back to legacy `Dispatcher` if Symfony routing fails (`iadmin/index.php` line ~93)
**API:**
- `webservice/dispatcher.php` — REST endpoint reached via `.htaccess` line ~323
**State Management:**
- Per-request state held in `classes/Context.php` singleton (`$context->cart`, `$context->customer`, `$context->shop`, `$context->controller`, `$context->language`)
- Persistent state in MySQL (`materac_*` tables)
- Caching configured but disabled (Memcached driver inactive)
## Key Abstractions
**Module:**
- Purpose: Attachable feature unit registering hook callbacks
- Examples: `modules/crosssellpro/crosssellpro.php`, `modules/paynow/paynow.php`, `modules/fbpixel/fbpixel.php`
- Pattern: subclasses `Module` (`classes/Module.php`); `install()` registers hooks; `hook<Name>()` methods are invoked dynamically
**ObjectModel:**
- Purpose: ORM base for domain entities — handles `add()`, `update()`, `delete()`, hydration, validation
- Examples: `classes/Product.php`, `classes/Cart.php`, `classes/Order.php`
- Pattern: declarative `$definition` array describes table + fields
**Hook (publish/subscribe):**
- Purpose: Decouple core from extensions
- Examples: `displayShoppingCartFooter`, `displayCheckoutSummaryTop`, `displayHeader`, `actionFrontControllerSetMedia`
- Pattern: `Hook::exec('hookName', ...)` dispatches; `$module->hookHookName(...)` receives
**Context:**
- Purpose: Per-request global container (cart, customer, shop, language, controller)
- Pattern: Singleton — `Context::getContext()`
**Override:**
- Purpose: Patch core class behavior without editing core
- Examples: `override/classes/Cart.php` (custom totals), `override/classes/Hook.php` (PageCache hooks)
- Pattern: Files in `override/classes/<X>.php` override `classes/<X>.php` via PrestaShop autoloader
## Entry Points
**Front-office:**
- Location: `index.php` (root) — invoked implicitly via `.htaccess`
- Triggers: Any non-physical URL on the public domains
- Responsibilities: Bootstrap → Dispatcher → controller dispatch
**Back-office:**
- Location: `iadmin/index.php` (custom-named admin folder)
- Triggers: Admin URL access
- Responsibilities: Boot `AppKernel`, fall back to legacy dispatcher when needed
**Web service:**
- Location: `webservice/dispatcher.php`
- Triggers: REST API calls
**Custom Cross-Sell module:**
- Location: `modules/crosssellpro/crosssellpro.php`
- Hooks registered (in `install()`): `displayShoppingCartFooter`, `displayCheckoutSummaryTop`, `displayHeader`, `actionFrontControllerSetMedia`
- Key methods: `hookDisplayShoppingCartFooter()`, `hookDisplayCheckoutSummaryTop()`, `buildCrossSellProducts()`, `collectAccessoryIds()`, `getCombinationFlags()`, `presentProducts()`
- Templates: `modules/crosssellpro/views/templates/hook/cartCrossSell.tpl`, `…/checkoutCrossSell.tpl`
- Front assets: `modules/crosssellpro/views/css/cartCrossSell.css`, `modules/crosssellpro/views/js/cartCrossSell.js`
## Error Handling
**Strategy:** PrestaShop legacy approach — `Tools::displayError()`, exceptions logged to `iadmin/errors.log` and `errors.log` (root). Exceptions in hooks may silently degrade module output.
**Patterns:**
- `try/catch` rare in custom code; `import-product.php` lacks transaction wrapping/rollback
- Mail send paths in `buy-by-phone.php` minimally handle PHPMailer failures
- `errors.log` at repo root + `iadmin/errors.log` (5 MB historical) — both inside webroot (concern, see `concerns.md`)
## Cross-Cutting Concerns
**Logging:**
- File-based: `errors.log` (root), `iadmin/errors.log`
- Symfony Monolog wired in admin via `MonologBundle`
**Caching:**
- `pagecache` module
- Memcached configured but disabled (`app/config/parameters.php`)
- Smarty compiled templates: `themes/leo_gstore/cache/`
**Authentication:**
- Customer auth: PrestaShop core (cookies + DB)
- Admin auth: Symfony Security via `iadmin/`
**Image handling:**
- WebP delivery via `.htaccess` content negotiation + `modules/x13webp/`
- Lazy-loading image override (`pshowlazyimg` override)
---
*Architecture analysis: 2026-05-10*
*Update when major patterns change*