it-bens/shopware-sdk-bundle
Symfony bundle that integrates the Shopware SDK into your application, wiring configuration and dependency injection so you can use Shopware Admin and Storefront API clients through convenient Symfony services.
1. Install and register
composer require it-bens/shopware-sdk-bundle:^0.7
0.x versioning means minor bumps can break you — pin the exact version you tested (0.7.0) rather than a floating constraint, and commit composer.lock.
There is likely no Symfony Flex recipe (0 dependents, 2 stars), so verify and register manually in config/bundles.php:
ItBens\ShopwareSdkBundle\ShopwareSdkBundle::class => ['all' => true],
2. Configure credentials
Never inline secrets; use env/.env.local or Symfony Secrets (bin/console secrets:set SHOPWARE_ADMIN_CLIENT_ID).
# config/packages/it_bens_shopware_sdk.yaml
it_bens_shopware_sdk:
shop_url: '%env(SHOPWARE_SHOP_URL)%'
cache: 'shopware.cache' # v0.7.0+: a service id, or null to disable
# admin + store credentials — dump the exact keys:
# bin/console config:dump-reference it_bens_shopware_sdk
cache changed shape in v0.7.0: it is no longer true/false. Point it at a cache pool service id (e.g. a dedicated cache.app.shopware pool) or null to turn caching off. A leftover cache: true will fail config validation.
3. See what was registered
bin/console debug:container it_bens --show-private
bin/console debug:config it_bens_shopware_sdk
The bundle's job is broader than it looks: it registers/autowires the SDK's AdminApi and StoreApi services with your credentials, wires the configured cache, and collects entity-definition collection populators (see Implementation Patterns). Everything else is the SDK.
4. First use case
A one-off console command is the safest first integration: it exercises auth, config, and HTTP without touching request/response cycles.
#[AsCommand(name: 'app:shopware:smoke-test')]
final class SmokeTestCommand extends Command
{
public function __construct(private readonly AdminApi $adminApi) { parent::__construct(); }
protected function execute(InputInterface $in, OutputInterface $out): int
{
// Example call surface — confirm against the SDK README
$result = $this->adminApi->get('/api/_info/version');
$out->writeln(json_encode($result));
return Command::SUCCESS;
}
}
If that returns a version, the bundle is wired correctly. If it throws a 401, your admin client id/secret are wrong; a 404 usually means shop_url is wrong (subdirectory/trailing slash).
Keep the SDK behind your own interface. Given the adoption footprint, treat this bundle as an implementation detail. Wrap it:
interface ProductFetcherInterface { public function fetch(string $id): ProductDto; }
final class ShopwareProductFetcher implements ProductFetcherInterface
{
public function __construct(private readonly AdminApi $adminApi) {}
public function fetch(string $id): ProductDto
{
$raw = $this->adminApi->get('/api/product/' . $id);
return ProductDto::fromArray($raw); // map to your own DTOs, not SDK types
}
}
Controllers, handlers, and Twig never see SDK classes. Swapping the SDK (or upgrading it) becomes a one-file change.
Split Admin vs Store. The bundle exposes two API services with different credentials and semantics:
StoreApi → storefront-facing reads (products, categories, checkout), uses the Store API access key, respects sales-channel scoping.AdminApi → full CRUD, uses OAuth client credentials, should only be reachable from back-office/console/worker code.Put admin calls behind an interface bound only where needed, so storefront requests can't accidentally use admin credentials.
Entity definition collection populators (v0.7.0). The bundle collects "entity definition collection populators" — classes that contribute entity definitions to the SDK — and v0.7.0 changed how they're discovered. It used to scan the DI container; it now relies on the AsEntityDefinitionCollectionPopulator attribute/interface:
#[AsEntityDefinitionCollectionPopulator]
final class MyEntityDefinitionCollectionPopulator implements EntityDefinitionCollectionPopulatorInterface
{
// ...
}
Practical consequences: keep autoconfigure: true on the service definitions in the directory where these live, or tag them explicitly. Anything that previously "just worked" via container scanning while sitting in a non-autoconfigured or manually-registered service block will silently stop being collected. Verify with debug:container --tag=<tag from the attribute> after upgrading.
Dedicated cache pool. Since v0.7.0 the cache target is a service id, define an explicit pool so token/entity caches don't share eviction pressure with your app cache:
# config/packages/cache.yaml
framework:
cache:
pools:
shopware.cache:
adapter: cache.adapter.redis
default_lifetime: 600
Go async for anything bulk. Shopware Admin API is rate-limited and slow under load. For imports, syncs, and webhooks use Messenger:
#[AsMessageHandler]
final class SyncOrderHandler
{
public function __construct(private readonly AdminApi $adminApi) {}
public function __invoke(SyncOrder $message): void
{
$this->adminApi->post('/api/order', $message->payload);
}
}
Configure a retry strategy on the transport (retry_strategy: max_retries: 3, delay: 1000, multiplier: 2). Shopware's admin tokens expire (~10 min default); in long-running workers, expect occasional 401s and retry rather than assuming a token lives forever.
Config as a service layer. Bind shop-specific parameters (sales channel id, default currency, etc.) as container parameters and inject them into small "Shopware client" services per concern (OrderClient, ProductClient) rather than passing raw endpoint paths around the codebase.
Testing. Mock your own interfaces in unit tests. For light integration tests, the SDK accepts custom HTTP clients in most versions — check the bundle's service definition; if the HTTP client is a registered service you can decorate or replace it in config/services_test.yaml with MockHttpClient to avoid hitting a real shop. Point cache: null in config/packages/test/ so tests don't read stale tokens from a shared pool.
Multi-shop. The bundle config is global (one shop). For multi-tenant setups, build your own factory that instantiates SDK API clients directly per shop and register them as keyed services — don't try to fight the bundle's single-instance autowiring.
cache config. true/false are gone. Use a cache pool service id or null. Grep your configs (including config/packages/*/) before bumping — a cache: true left in a per-env file is a hard config failure at container compile, not a runtime warning.config/packages/it_bens_shopware_sdk.yaml supplying client_id while config/packages/prod/... only overrides client_secret. Override whole credential blocks, or — better — keep every value as an %env()% reference and vary only .env files per environment.AsEntityDefinitionCollectionPopulator interface/attribute. If your populators live in a bundle or a services.yaml entry with autoconfigure: false, they will no longer be collected — the failure is silent (no error, just missing definitions). Re-check after upgrading and add the tag manually if needed.composer.json, commit composer.lock, and read the changelog before bumps. Three behaviour changes in one minor release is the pattern to plan for: budget time for a fork if it stalls.config/bundles.php and create the config file by hand. config:dump-reference is your source of truth for key names, not blog posts.shop_url format is the #1 cause of failures. Include the scheme, exclude the trailing slash, and include any subdirectory (https://shop.example.com/shopware). Most 404s are here.verify_peer/cacert rather than disabling TLS globally.cache:clear costs you a re-auth. Clearing the pool forces a fresh OAuth token and a fresh entity-definition collection. Harmless, but expect a latency spike after deploy; don't be surprised by a burst of token requests.$e->getPrevious() and any response body, and assert status codes in tests — otherwise Shopware's verbose error payloads (which contain the actual field validation message) are lost.messenger:consume loops.debug:container --show-private --tag=... reveals the SDK service ids and the populator tag. You can decorate AdminApi/StoreApi with a logging decorator (great for auditing admin writes) or use a compiler pass to swap the HTTP client. Prefer decoration over editing the bundle's config.shopware/api-client lineage, and 0.x releases are not shy about changing config shape. Confirm the SDK's supported Shopware 6.x range against your target before committing — mismatched minor versions on endpoints like /api/product cause subtle 400s.How can I help you explore Laravel packages today?