# 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//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()` 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()` 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()` 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/.php` override `classes/.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*