DEV Community

Cover image for How to customize Aimeos e-commerce projects without maintaining a fork
Aimeos
Aimeos

Posted on

How to customize Aimeos e-commerce projects without maintaining a fork

Editing a file under vendor/ can solve today's problem and create the next upgrade's problem.

The first change usually looks harmless: move a block on the product page, add a checkout rule, adjust the way products are loaded. After a few more, the shop depends on a private fork and every Composer update begins with a three-way diff.

Aimeos provides a separate place for each kind of change. Wording can stay in translation configuration, page composition in page configuration, markup in a template and business rules in decorators. Larger features can live in their own Composer package.

When a new requirement arrives, start by finding the narrowest layer that can handle it.

Choose the smallest extension point

Before writing a class, identify the layer that owns the requirement.

Requirement Use
Change a label, page size or feature switch config/shop.php
Add or remove a storefront component page configuration
Change the Laravel page shell an overridden Blade view
Restyle the shop a custom theme
Change component markup a template override
Store project-specific product data properties or attributes
Add behavior around existing logic a decorator
Replace an implementation completely a configured class replacement
Add new storage, behavior or an integration a project-specific Aimeos extension

Use the first row that solves the problem. There is little value in creating a subclass for a configuration change or copying a controller when a decorator can add the required behavior. Related project code is also easier to follow when it lives in one extension.

Start with configuration

The Laravel integration exposes the shop configuration in config/shop.php. Its nested arrays follow the structure of Aimeos components and controllers, so the location of a setting identifies the part of the system it affects.

Suppose a store needs 24 products per page, no add-to-basket button in product lists, a two-level mega menu and “Brands” instead of “Suppliers”. The relevant configuration is short:

'client' => [
    'html' => [
        'catalog' => [
            'lists' => [
                'basket-add' => false,
                'size' => 24,
            ],
        ],
    ],
],

'controller' => [
    'frontend' => [
        'catalog' => [
            'levels-always' => 2,
        ],
    ],
],

'i18n' => [
    'en' => [
        'client' => [
            'Suppliers' => ['Brands'],
        ],
    ],
],
Enter fullscreen mode Exit fullscreen mode

No service provider, listener or copied component is involved.

Some structural changes are environment switches. Multi-language routes, vendor-specific sites and seller registration can be enabled without rebuilding the routing layer:

SHOP_MULTILOCALE=true
SHOP_MULTISHOP=true
SHOP_REGISTRATION=true
Enter fullscreen mode Exit fullscreen mode

For many projects, config/shop.php is where customization starts.

Compose pages from components

A storefront page is assembled from components. A product detail page can contain locale selection, the mini basket, category navigation, search, a stage area, the product itself and session-based lists such as recently viewed products.

The list lives in config/shop.php:

'page' => [
    'catalog-detail' => [
        'locale/select',
        'basket/mini',
        'catalog/tree',
        'catalog/search',
        'catalog/stage',
        'catalog/detail',
    ],
],
Enter fullscreen mode Exit fullscreen mode

This version leaves out catalog/session. The page no longer prepares the recently viewed and pinned-product area, but the product-detail component remains untouched.

You can also add an existing component to a page, or create a Laravel route and render the required Aimeos components through the Shop facade.

The Laravel extension guide documents the available page sections, routes and facade integration.

Keep the page shell, markup and theme separate

Aimeos separates presentation into three layers. Blade views define the surrounding Laravel page, component templates generate the storefront markup, and themes provide CSS, JavaScript, fonts and images.

To change the complete product-detail page shell, copy the corresponding package view to the application's vendor-view directory:

resources/views/vendor/shop/catalog/detail.blade.php
Enter fullscreen mode Exit fullscreen mode

Laravel prefers the application copy, leaving the package file untouched.

If the page shell is already correct but the product markup is not, override only the relevant template in the project extension:

templates/client/html/catalog/detail/body.php
Enter fullscreen mode Exit fullscreen mode

An identically named project template takes precedence over the default. A custom image gallery may need only catalog/detail/image.php; it does not require a copy of the entire detail component.

If the HTML structure is already right, a theme is enough. Aimeos keeps assets such as catalog-detail.css and basket-mini.js per component, so pages load the assets for the components they use.

The size of the override matters during upgrades. Replacing image.php instead of body.php, for example, leaves less upstream code to compare.

During development, clear the Aimeos and compiled Blade caches if configuration or template changes do not appear:

php artisan aimeos:clear
php artisan view:clear
Enter fullscreen mode Exit fullscreen mode

The official guides cover both template overrides and custom themes.

Add business rules with decorators

Many project rules only need to run around existing behavior. Typical examples include a regional availability check, an audit event, data from an external service or validation before an item is stored.

A decorator wraps the existing object, changes only the relevant methods and delegates everything else.

For example, a product-controller decorator can check products retrieved individually through get() against an application service owned by the Laravel project:

namespace Aimeos\Controller\Frontend\Product\Decorator;

use App\Commerce\AvailabilityRule;
use Aimeos\MShop\Product\Item\Iface;

final class Availability extends Base
{
    public function get(string $id): Iface
    {
        $item = parent::get($id);

        app(AvailabilityRule::class)->assertPurchasable($item);

        return $item;
    }
}
Enter fullscreen mode Exit fullscreen mode

AvailabilityRule is ordinary Laravel application code. The decorator connects it to the Aimeos product controller.

The class belongs at:

src/Controller/Frontend/Product/Decorator/Availability.php
Enter fullscreen mode Exit fullscreen mode

Register it in config/shop.php:

'controller' => [
    'frontend' => [
        'product' => [
            'decorators' => [
                'local' => ['Availability'],
            ],
        ],
    ],
],
Enter fullscreen mode Exit fullscreen mode

Only get() changes. Product searches, aggregation and the remaining controller API continue through the original implementation. This example therefore guards direct product retrieval only. If the rule determines whether a product may be purchased, enforce it again at basket or checkout level.

HTML clients and data managers support the same pattern. Several decorators can be combined without collecting unrelated rules in one project class.

When the standard behavior is unsuitable, Aimeos factories also support named replacements. Setting controller/frontend/product/name to Project, for example, selects Aimeos\Controller\Frontend\Product\Project. This is useful when a decorator would have to replace most of the original implementation anyway.

Put larger customizations in one package

Once a customization contains several classes, templates, translations or database changes, it should become a project-specific Aimeos extension.

Before extending a core table, check whether the requirement fits an Aimeos property, attribute or related domain item. Much project-specific product data can be stored without changing the core schema.

The standalone distribution already declares packages/* as a Composer path repository. An extension can live there during development and be installed like any other dependency:

composer require acme/shop-extension
Enter fullscreen mode Exit fullscreen mode

A typical project extension contains:

packages/shop-extension/
├── composer.json
├── manifest.php
├── config/
├── i18n/
├── setup/
├── src/
├── templates/
└── themes/
Enter fullscreen mode Exit fullscreen mode

The manifest registers the resources contributed by the package. The setup/ directory carries schema updates, Composer handles autoloading, and the extension can be versioned independently from both Laravel and Aimeos.

For a single shop, this keeps its Aimeos-specific code in one place. Organizations running several shops can use the same package to share an integration, theme or business rule between applications.

The extension documentation explains manifests and package structure, while the extension generator creates the initial files.

Know when to replace the HTML frontend

Template overrides work well while the application still fits the component model. If nearly every page needs replacing, or the same commerce backend must serve web, mobile and in-store clients, the APIs are usually the cleaner option.

Aimeos exposes storefront data through JSON:API and administration through GraphQL. The product, price, stock, basket and order logic can remain in place while a new client replaces the presentation.

A team does not have to make that change all at once. It can keep the working storefront, override a few templates and use the APIs only for channels that need a separate interface.

What the structure costs

The first extension takes some orientation. Configuration paths correspond to class and template paths, and the naming convention is more explicit than a generic Laravel hook.

Once controller/frontend/product/decorators/local has led to the product controller's decorator directory, the same relationship repeats throughout the platform. The paths make it possible to see who owns the behavior and how the project changed it.

Separation does not make custom code immune to upstream changes. Templates, decorators and replacement classes should still be reviewed during major upgrades, but the affected surface is explicit and confined to the project extension.

Upstream code stays in vendor/, project code stays in the project extension, and updates require a focused compatibility review rather than a source-code merge.

Each kind of change has an obvious home. By using the narrowest one that fits, a shop can move a long way from the Aimeos defaults without becoming a fork.

Top comments (0)