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

7.8 KiB

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