shopware/core
Shopware 6 Core is the PHP/Symfony foundation of the open-source Shopware e-commerce platform, providing the framework, data abstraction, and business logic to build scalable storefronts, APIs, and extensions.
First, calibrate expectations: shopware/core is not a Laravel package and there is no service provider or facade to register. It is the framework layer of Shopware 6, a Symfony-based e-commerce platform. You do not composer require it into a Laravel app; it is pulled in as a dependency by the shopware/production (or shopware/development) skeleton, and you build on top of the platform via plugins, apps, and themes.
Minimal path:
composer create-project shopware/production my-shop
cd my-shop
# configure .env (DATABASE_URL, APP_URL, APP_SECRET)
bin/console system:install --create-database --basic-setup
bin/console plugin:create MyPlugin # scaffolds a plugin skeleton
The GitHub repo you are reading is a read-only split used for the Composer package; issues and PRs live in shopware/shopware. Treat the split repo as a reference copy, not the contribution target.
Where to look first, in order:
src/Core/Framework/DataAbstractionLayer — the DAL is the heart of the package; almost everything you do reads or writes entities through it.src/Core/Checkout — cart, order, and checkout pipeline.src/Core/System — sales channels, currencies, configuration, custom fields.src/Core/Content — products, media, CMS.First real use case for a Laravel developer: build a small plugin that adds a custom entity and exposes it through the Store API. That single exercise touches DI, migrations, entities, repositories, routing, and the sales channel context — the five things you will use every day.
Mental model mapping. Symfony's container ≈ Laravel's container; EventDispatcherInterface + EventSubscriberInterface ≈ Laravel events/listeners; the DAL is roughly "Eloquent plus a query object," but with explicit Criteria instead of a fluent builder and mandatory Context on every call.
Plugins are the unit of work. A plugin extends Shopware\Core\Framework\Plugin and implements lifecycle hooks (install, update, activate, deactivate, uninstall). Services live in Resources/config/services.xml with autowire/autoconfigure; many core interfaces (EntityDefinition, EventSubscriberInterface, commands, scheduled task handlers) are collected by tag or autoconfiguration, so you rarely register them manually.
DAL as daily driver. Define an Entity, EntityCollection, and EntityDefinition; register a PluginMigration for the schema. Inject EntityRepository and build Criteria:
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', true))
->addAssociation('manufacturer')
->addSorting(new FieldSorting('name'))
->setLimit(50);
$result = $this->productRepository->search($criteria, $context);
For adding fields to core entities (products, orders, customers) use EntityExtension plus a migration — never edit vendor code.
Events over decoration, decoration over core edits. Subscribe to DAL events (product.written, checkout.order.placed) or business events for the Flow Builder. When you need to change behavior of an existing service, use Symfony service decoration (decorates= in services.xml) rather than patching. Decoration is the sanctioned extension point.
Context discipline. In storefront and Store API code, the context is SalesChannelContext, which carries the sales channel, currency, language, customer group, and rules. In admin/CLI code it is Context (usually Context::createDefaultContext() or Context::createCLIContext()). Getting this wrong silently produces wrong prices, wrong translations, or missing configuration.
Configuration comes from SystemConfigService::get('MyPlugin.config.someKey', $salesChannelId), with $salesChannelId from the sales channel context. Snippets go in Resources/snippet/*.json; admin JS in Resources/app/administration.
Routes. Storefront controllers extend StorefrontController and render Twig; Store API controllers return StoreApiResponse and are scoped with #[RouteScope(scopes: ['store-api'])]; admin endpoints use ApiController and /api/. The scope must match how you authenticate.
Background work. Long jobs go on the message queue; recurring jobs extend ScheduledTask with a ScheduledTaskHandler. Both require a running bin/console messenger:consume worker (or the platform's cron/worker setup), which trips up people who test only in the request cycle.
Testing. Use TestBootstrapper in the plugin's bootstrap, then the KernelTestBehaviour, IntegrationTestBehaviour, and DatabaseTransactionBehaviour traits from the core test case base. Unit-test pure services; integration-test anything touching the DAL.
search() without a limit returns everything. Always setLimit() unless you genuinely want the full set, and prefer searchIds() for large scans. For accurate pagination totals use setTotalCountMode(Criteria::TOTAL_COUNT_MODE_EXACT), otherwise totals can be capped.Context into storefront logic is the most common source of "why is the price wrong / translation missing / rule not applied." Fetch SalesChannelContext from the route argument or SalesChannelContextService.new Context(...). Mixing default and CLI contexts across a transaction can produce surprising behavior.bin/console database:migrate --all must run after plugin install/update, plus cache:clear and usually theme:compile and administration:build. Forgetting any of these produces stale services or missing assets.shopware/* Composer packages must be on the same minor version. Do not hand-pin shopware/core independently of the skeleton; use the skeleton's constraints. The repo's star count and "dependents: 0" are artifacts of the split repo — real-world usage is via Packagist and is enormous.messenger:consume. Add this to every local setup doc.Feature::isActive('v6.x.0.0'). When docs and behavior disagree, check Feature::getAll() in your running version before assuming a bug.product-{id}, etc.). If your custom entity writes should invalidate storefront pages, emit the right cache tags; otherwise stale pages will outlive your change.bin/console debug:container, debug:event-dispatcher, and debug:router are your php artisan route:list/tinker equivalents. Run with APP_ENV=dev and wire Xdebug to the profiler — there is no Telescope.EntityRepository::search with an explicit Context and accept the request-scoped cache semantics.vendor/. Every "quick fix" belongs in an extension, a decoration, or an event subscriber. If you find yourself unable to hook a behavior, search src/Core for the dispatched event before concluding it is not extensible.How can I help you explore Laravel packages today?