Maho Ecommerce Platform

repository·main·Indexed 40 days ago

https://github.com/mahocommerce/maho

A modern, open-source ecommerce platform built on PHP 8.5+ and Symfony, designed as a high-performance replacement for legacy Magento 1 projects. It features native OpenTelemetry instrumentation for distributed tracing, logs, and metrics, as well as a theme system based on Tailwind CSS 4 and DaisyUI 5.

Tokens
37.3K
Snippets
146
Records
177
Agent score
99%

What's inside Maho

  1. Overview of the Maho tech stack

    main

    Maho is a modern ecommerce platform designed as a drop-in replacement for Magento 1 projects. It utilizes a high-performance, modern PHP stack:

    • Runtime: PHP 8.5+ (utilizing strict types and attributes).
    • Database: Doctrine DBAL 4 (supports MySQL, MariaDB, PostgreSQL, and SQLite).
    • Core Components: Symfony (HTTP, caching, console, validation, mailer, etc.) and Laminas.
    • Frontend: 100% vanilla JavaScript (no jQuery or legacy frameworks like Prototype.js).
    • Logging & PDF: Monolog for structured logging and DomPdf for PDF generation.
    • Architecture: Clean MVC, event-driven, and modular design.
  2. Configure Logs and Metrics Export

    main

    Maho can export logs and metrics via OTLP/HTTP:

    • Logs: Requires open-telemetry/opentelemetry-logger-monolog. Every Monolog record is shipped to /v1/logs and is tagged with trace_id and span_id. Warning: This exports Monolog records verbatim; ensure your OTLP backend is as secure as your local log files.
    • Metrics: Ships delta-temporality metrics to /v1/metrics. Supported metrics include:
      • http.server.request.duration (histogram)
      • maho.orders (counter)
      • maho.order.revenue (counter)
      • maho.cart.additions (counter)

    Telemetry is flushed after the response is sent to the client to minimize latency impact. However, sequential flushing of multiple signals can increase the time a PHP worker is held if the collector is slow.

  3. Understand the Maho Theme System Architecture

    main

    The Maho Theme System uses a modern stack of Tailwind CSS 4 and DaisyUI 5. It is designed so that store owners can use industry-specific variants (like fashion or electronics) without needing Node.js, as these variants are provided as plain CSS.

    Key Architectural Concepts

    • No Template Forking: The system uses compiled CSS to style semantic class contracts in base/default templates. This means template fixes in base/default automatically propagate to all industry themes.
    • Dual Stylesheet Loading: Every page loads two stylesheets:
      1. css/styles.css: The compiled global engine (shared by all themes).
      2. css/theme.css: The identity/palette of the active theme.
    • Theme Fallback: Industry themes (e.g., fashion/) inherit templates from base/default/. They primarily provide css/theme.css to override CSS variables.
    • Composer Integration: When installed via Composer, the maho-composer-plugin copies the theme files into your project's public/ directory. Never edit files in base/default directly in a child project, as they are overwritten during updates. Instead, create your own theme.
  4. Manage Data Safety and PII in Traces

    main

    Maho implements several safeguards to protect sensitive data, but certain configurations can increase exposure:

    Default Protections

    • No Sensitive Headers/Bodies: HTTP request/response bodies, headers, query strings, and CLI arguments are never exported.
    • No PII in Commerce Events: Span events use maho.* attributes which contain no PII. Users are identified only by enduser.id.
    • Exception Safety: Failed spans include the exception class in error.type, but the exception message is only included in the standard OTel exception event. Treat your OTLP backend as a location that holds whatever is in your exception messages.

    High-Exposure Settings

    • Query Statement: By default, the SQL statement is included in every query span. Because Maho uses quoteInto() rather than parameter binding, the statement contains actual values (e.g., customer emails, search terms, or password reset tokens). Disable this if your backend must not hold customer data.
    • Export Logs: Enabling log export ships Monolog records verbatim. Anything written to the logs by any module will be sent to your OTLP backend.
  5. Configure Distributed Tracing and Context Propagation

    main

    Maho supports distributed tracing across different components:

    • Outgoing HTTP Requests: Requests made via \Maho\Http\Client::create() automatically include a traceparent header. The baggage header (containing maho.store and maho.currency) is only sent to hosts explicitly listed under Baggage Hosts in the admin.
    • Queue Messages: When a message is dispatched to a queue, the current trace context is stored on the message row. The consumer handler's spans will join the original request's trace.
    • Upstream Traces: By default, Maho does not continue traces started by upstream callers. To enable this, activate Trust Incoming Trace Headers in the admin. This should only be done behind a trusted proxy. The headers read depend on OTEL_PROPAGATORS.
    • Frontend Correlation: Enabling the Server-Timing Response Header exposes the W3C trace context (trace id, span id, and sampled flag) to the browser, allowing correlation between frontend RUM tools (like Grafana Faro) and backend traces.
  6. Handle DaisyUI component collisions and styling

    main

    Maho uses several semantic classes that may collide with DaisyUI component names.

    Known Collisions:

    • .label
    • .footer
    • .loading
    • .tab-content
    • .breadcrumbs

    How to resolve collisions: To fix a collision, add unlayered rules at the bottom of src/_components.css.

    Special Component Styling:

    • Star Ratings: The star rating component (emitted by Mage_Rating_Helper_Data::getStarsHtml()) is the standard DaisyUI rating component. It is styled using the --maho-color-rating CSS variable.
    • CMS/Database Content: Because DaisyUI components used in CMS content cannot be scanned at build time, a curated safelist is maintained in src/tailwind.css to ensure common components like btn, badge, alert, and card remain available.
  7. How color palettes and dark mode work in Maho themes

    main

    Maho themes use CSS variables to define identity. The default maho identity is defined in default/src/_theme.css as a DaisyUI theme, while industry themes use plain CSS variables in css/theme.css.

    Dark Mode Implementation: Dark mode is handled via @media (prefers-color-scheme: dark).

    • Some themes (like food and kids) are pinned to light mode by setting color-scheme: light inside the dark media block.
    • Merchants can disable dark mode via the admin: System > Configuration > Design > Theme Settings > Dark Mode. When disabled, the html element receives data-color-scheme="light", and dark rules are scoped using :where() to maintain cascade order.

    Customizing Colors:

    1. Use the Admin settings for basic color changes.
    2. Override variables in your theme's css/theme.css.
    3. To use a DaisyUI stock theme (e.g., dracula), use Option B (a custom build) and name it in a @plugin "daisyui" block.
  8. Understand what is traced in Maho

    main

    Maho automatically instruments several layers of the application. Tracing begins only when the request root span opens to avoid scattered single-span traces during bootstrap.

    Trace Categories

    SpanKindNotes
    {METHOD} {module/controller/action}SERVERRequest root span, renamed after routing
    {METHOD} {api_route}SERVERSame for /api/*: the API Platform route name (REST, GraphQL, MCP) or api/{type} for the legacy servers
    {OPERATION} {table}CLIENTEvery DB query. db.query.text carries the statement as executed
    {METHOD} (HTTP client)CLIENTOutgoing requests via \Maho\Http\Client::create(); url.full is stripped of query string, fragment and userinfo
    process {MessageClass}CONSUMEROne span per queue message, continuing the trace of the request that dispatched it
    BLOCK:*, OBSERVER:*, cron.job*, email.send, image.process, index.reindex, payment.*INTERNALHigh-level profiler timers
    cache.*INTERNALCache reads and writes, off by default
    maho {command}INTERNALEach CLI command is its own trace

    Commerce Moments

    Commerce events are recorded as span events with maho.* attributes (no PII):

    • maho.order.placed
    • maho.cart.add
    • maho.checkout.success
    • maho.customer.login

    Logged-in users are tagged with the pseudonymous enduser.id.

  9. Install Maho OpenTelemetry

    main

    To enable native OpenTelemetry instrumentation in Maho, install the core SDK and OTLP exporter via Composer. nyholm/psr7 is required to provide the PSR-17 HTTP factories used by the OTLP transport.

    If you also want to export logs, install the Monolog integration package.

    composer require open-telemetry/sdk open-telemetry/exporter-otlp nyholm/psr7
    
    # optional, for log export:
    composer require open-telemetry/opentelemetry-logger-monolog
  10. Install Maho via Composer

    main

    To start a new Maho project, use the composer create-project command with the maho-starter package. This will scaffold a new project directory with the necessary dependencies.

    composer create-project mahocommerce/maho-starter yourproject
  11. Option A: Create a theme using pure CSS (no build tools)

    main

    Use this approach for defining a brand identity (colors, fonts, shapes) without writing custom templates or using Tailwind classes. This is the method used by Maho's industry themes.

    1. Create public/skin/frontend/[base]/[theme_name]/css/theme.css.
    2. Override design tokens within the :root selector. These tokens control everything from typography to component shapes and product tile backgrounds.
    3. Add custom CSS below the tokens. Because theme.css is unlayered, your selectors will always win over the compiled framework.

    Key Design Tokens:

    • --color-primary, --color-base-100, --color-base-content (Core colors)
    • --font-display, --font-body (Typography)
    • --radius-field, --radius-box (Shapes)
    • --product-tile-bg, --product-cutout-shadow (Product presentation)
    • --footer-bg, --footer-ink (Footer identity)

    Example css/theme.css structure:

    :root {
        --font-display: 'Figtree', sans-serif;
        --color-primary: #0e7a5f;
        --color-base-100: #ffffff;
        --radius-field: 0.5rem;
        /* ... other tokens ... */
    }
    
    /* Custom overrides */
    .btn { border-radius: 9999px; }
  12. Declare a theme in theme.xml

    main

    To manually create or define a theme, you must first declare it in app/design/frontend/[base]/[theme_name]/etc/theme.xml. Use the <fonts> node to load web fonts via <preconnect> and <stylesheet> to ensure the browser preloads them efficiently.

    Example for a 'pharmacy' theme:

    <theme>
        <parent>base/default</parent>
        <title>Maho - Pharmacy</title>
        <fonts>
            <preconnect>https://fonts.bunny.net</preconnect>
            <stylesheet>https://fonts.bunny.net/css2?family=Figtree:wght@400;600;700&amp;display=swap</stylesheet>
        </fonts>
    </theme>