Magento 2 / Adobe Commerce Module Development
Overview
Magento 2 (Adobe Commerce) is a modular, service-oriented e-commerce platform built on PHP, Symfony components, and a layered architecture. Every piece of functionality is delivered through modules, and the framework provides well-defined extension mechanisms -- plugins (interceptors), observers, preferences, and layout XML overrides -- that allow third-party code to modify behavior without touching core files.
The platform separates concerns into distinct layers: the Service Layer (service contracts and repositories), the Domain Layer (models and resource models), the Persistence Layer (database interaction via resource models and declarative schema), and the Presentation Layer (blocks, templates, layout XML, UI components). Modules communicate through dependency injection, event dispatching, and service contracts rather than direct class instantiation.
Understanding this architecture is essential before writing any Magento extension. Direct instantiation of objects via new or the ObjectManager is strongly discouraged. All dependencies must be injected through constructor injection, configured in di.xml files, and scoped to the appropriate area (global, frontend, adminhtml, webapi_rest, webapi_soap, crontab, graphql).
For full module directory structure, registration files, declarative schema, and service contracts, see references/module-architecture.md.
Module Structure
Every Magento module requires two registration files:
registration.php -- Registers the module with the Magento component registrar. This file is auto-loaded by Composer and tells Magento where to find the module.
etc/module.xml -- Declares the module name, setup version, and dependencies (sequence) on other modules. The sequence node controls load order, ensuring that modules the extension depends on are loaded first.
composer.json -- Defines the Composer package for the module, including its PSR-4 autoload namespace, dependencies on other Magento modules (as Composer packages), and PHP version requirements.
A minimal module lives under app/code/VendorName/ModuleName/ during development. For distribution, package the module as a Composer package installable via composer require vendor/module-name.
After creating these files, run bin/magento setup:upgrade to register the module in the database, then bin/magento setup:di:compile to generate interceptors and proxies.
See examples/basic-module/registration.php and examples/basic-module/etc-module.xml for working registration files.
Dependency Injection
Magento 2 uses constructor-based dependency injection throughout the framework. The di.xml configuration file controls which concrete classes implement which interfaces, configures virtual types, and declares plugins.
Core Concepts
- Type preferences -- Map an interface to a concrete implementation. When any class requests
VendorName\ModuleName\Api\ServiceInterfacein its constructor, the ObjectManager injects the class specified in the<preference>node. - Virtual types -- Create a named variant of an existing class with different constructor arguments, without writing a new PHP class. Useful for configuring multiple instances of the same class with different parameters.
- Constructor arguments -- Override constructor argument values for specific classes using
<arguments>nodes indi.xml. Supply scalar values, arrays, or references to other types. - Proxy classes -- Delay instantiation of expensive dependencies until they are actually used. Declare a proxy in
di.xmlto wrap a dependency in a lazy-loading proxy. - Factory classes -- Auto-generated classes that create new instances of a given type. Use factories when creating multiple instances of a model (e.g., creating new product objects in a loop).
Scoping
Place di.xml files in the appropriate etc/ subdirectory to scope configuration:
| Scope | Path | When it applies |
|---|---|---|
| Global | etc/di.xml |
All areas |
| Frontend | etc/frontend/di.xml |
Storefront requests |
| Adminhtml | etc/adminhtml/di.xml |
Admin panel requests |
| Webapi REST | etc/webapi_rest/di.xml |
REST API requests |
| Webapi SOAP | etc/webapi_soap/di.xml |
SOAP API requests |
| Crontab | etc/crontab/di.xml |
Cron job execution |
| GraphQL | etc/graphql/di.xml |
GraphQL query execution |
For complete DI configuration patterns, proxy usage, and factory generation, see references/dependency-injection.md. For a working di.xml with plugin declaration, see examples/basic-module/etc-di.xml.
Extension Mechanisms
Magento provides three primary mechanisms for extending behavior: plugins, observers, and preferences. Each serves a different purpose and has distinct trade-offs.
Plugins (Interceptors)
Plugins intercept public method calls on any class that was instantiated through the ObjectManager. Three interception types are available:
- before -- Modify input arguments before the original method executes. The plugin method receives the same arguments as the original and returns a modified argument array.
- after -- Modify the return value after the original method executes. The plugin method receives the result and returns a modified result.
- around -- Wrap the original method entirely. The plugin method receives a
callable($proceed) that invokes the next plugin or the original method. Use around plugins sparingly -- they can break the call chain if$proceedis not called.
Plugins are declared in di.xml with a sortOrder attribute that controls execution priority. Lower sort order values execute first.
Observers
Observers respond to events dispatched throughout the Magento framework. Declare observers in events.xml and implement the ObserverInterface. Observers are best for reacting to events where modifying the return value is unnecessary.
Preferences
Preferences replace the entire implementation of a class or interface. A <preference> in di.xml tells the ObjectManager to instantiate the specified class whenever the original is requested. Use preferences when plugins and observers cannot achieve the goal.
When to Use Each
| Mechanism | Use case | Limitations |
|---|---|---|
| Plugin | Modify input/output of a public method | Cannot intercept final classes, final methods, non-public methods, static methods, __construct, virtual types |
| Observer | React to a system event | Cannot modify return values, limited to events that Magento dispatches |
| Preference | Replace an entire class implementation | Only one preference can be active per class; causes conflicts if multiple modules set preferences for the same class |
For detailed plugin syntax, observer event catalog, and real-world examples, see references/plugins-and-observers.md.
Development Setup
Project Installation
Create a new Magento project via Composer:
composer create-project --repository-url=https://repo.magento.com/ magento/project-community-edition magento2
Essential Commands
| Command | Purpose |
|---|---|
bin/magento setup:upgrade |
Run setup scripts, register new modules |
bin/magento setup:di:compile |
Generate interceptors, factories, proxies |
bin/magento setup:static-content:deploy |
Deploy static view files (CSS, JS) |
bin/magento cache:flush |
Clear all caches |
bin/magento cache:disable |
Disable specific cache types for development |
bin/magento deploy:mode:set developer |
Enable developer mode (errors displayed, no caching of generated code) |
bin/magento module:enable Vendor_Module |
Enable a specific module |
bin/magento module:status |
List enabled and disabled modules |
bin/magento indexer:reindex |
Rebuild all indexes |
Developer Mode
Always develop in developer mode (bin/magento deploy:mode:set developer). In developer mode, Magento displays errors, regenerates classes on the fly, and does not require static content deployment. In production mode, all code must be precompiled and static content pre-deployed.
Caching During Development
Disable full_page cache and block_html cache during development. Keep config, layout, and reflection caches enabled to avoid extremely slow page loads. Flush caches after modifying di.xml, events.xml, routes.xml, or layout XML files.
Key Integration Patterns
| Integration type | Key files | Reference |
|---|---|---|
| Payment method | config.xml, payment.xml, Gateway commands, Request/Response builders |
references/payment-method.md |
| Shipping carrier | Carrier model, config.xml, system.xml |
references/shipping-carrier.md |
| REST API endpoint | webapi.xml, service contract interfaces |
references/rest-and-graphql-api.md |
| GraphQL schema | schema.graphqls, resolver classes |
references/rest-and-graphql-api.md |
| Admin page | Controller, layout XML, UI components, menu.xml, acl.xml |
references/admin-ui-components.md |
| Storefront page | Controller, layout XML, blocks, .phtml templates |
references/storefront-layout-xml.md |
| Checkout customization | checkout_index_index.xml, JS components |
references/checkout-customization.md |
| Product type | Product type model, product_types.xml |
references/module-architecture.md |
| System configuration | system.xml, config.xml, adminhtml/system.xml |
references/admin-ui-components.md |
Anti-Patterns
Avoid these common mistakes that cause instability, upgrade failures, and marketplace rejection:
Direct ObjectManager Usage
Never call \Magento\Framework\App\ObjectManager::getInstance() directly in module code. The ObjectManager is an implementation detail of the DI container. All dependencies must be injected through the constructor. The only exceptions are in registration.php, factory/proxy generated code, and backward-compatible static methods in legacy code.
Bypassing Service Contracts
Always use repository interfaces (CustomerRepositoryInterface, ProductRepositoryInterface) instead of directly loading models via resource models. Service contracts provide a stable API that survives upgrades; direct model access may break when the database schema changes.
Ignoring ACL
Every admin controller must check ACL permissions in its _isAllowed() method (or via ADMIN_RESOURCE constant). Every REST/SOAP API endpoint must declare its required ACL resource in webapi.xml. Skipping ACL checks creates security vulnerabilities and causes marketplace rejection.
Modifying Core Files
Never edit files under vendor/magento/. Use plugins, observers, preferences, or layout XML overrides to modify core behavior. Core file modifications are overwritten on upgrade and make the installation unsupportable.
Overusing Around Plugins
Around plugins that forget to call $proceed() break the interceptor chain and can silently disable functionality in other modules. Prefer before/after plugins when possible. Use around plugins only when the original method must be conditionally skipped or its entire execution must be wrapped (e.g., adding a database transaction).
Hardcoded Values
Never hardcode store URLs, base paths, currency codes, or locale settings. Use Magento's configuration system (ScopeConfigInterface), store manager (StoreManagerInterface), and URL builder (UrlInterface) to retrieve these values dynamically.
Reference Files
- Module Architecture -- Directory structure, registration, declarative schema, service contracts, setup scripts
- Dependency Injection -- di.xml configuration, type preferences, virtual types, proxies, factories, scoping
- Plugins and Observers -- Interceptor system, events.xml, observer classes, event catalog, decision guide
- REST and GraphQL API -- webapi.xml, service contracts as endpoints, authentication, GraphQL resolvers
- Admin UI Components -- Admin routes, UI component grids and forms, menus, ACL, system configuration
- Storefront Layout XML -- Layout handles, containers, blocks, templates, themes, JavaScript components
- Checkout Customization -- Checkout UI components, custom steps, payment renderers, totals collectors
- Payment Method -- Payment gateway module structure, Gateway Command pattern, vault integration
- Shipping Carrier -- Carrier model, rate collection, tracking, multi-warehouse shipping
- Marketplace Publishing -- Adobe Commerce Marketplace submission, technical review, quality standards
Example Files
- examples/basic-module/registration.php -- Module registration file
- examples/basic-module/etc-module.xml -- Module declaration with sequence dependencies
- examples/basic-module/etc-di.xml -- DI configuration with plugin declaration
- examples/payment-method/PaymentMethod.php -- Payment Gateway model skeleton