Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Api Bundle Laravel Package

sylius/api-bundle

Sylius API Bundle integrates API Platform with Sylius to expose e-commerce resources through a REST/JSON-LD API. It provides resource configuration, serialization, filtering, pagination, and OpenAPI documentation for building custom storefront or admin clients.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this before you run composer require. sylius/api-bundle is a Symfony bundle, not a Laravel package. It ships as a read-only subtree split of src/Sylius/Bundle/ApiBundle out of the Sylius/Sylius monorepo, and it expects a Symfony HTTP kernel, the Sylius DI container, Doctrine ORM, and API Platform. There is no service provider, no facade, and no path to installing it into a Laravel app. The "5 stars / 0 dependents" numbers are a false signal from the split — real consumption flows transitively through sylius/sylius.

So the realistic starting point for a Laravel developer is: you do not install this package; you consume the API it defines. Practically, a Sylius instance (or Sylius Cloud) is running somewhere as a headless commerce service, and your Laravel app is the storefront/BFF/OMS calling its REST surface.

Before writing any code, resolve three questions:

  1. Which Sylius API are you talking to? Sylius 1.12.x ships sylius/api-bundle 1.12.x on API Platform 2.7; Sylius 2.x ships the API Platform 3/4 generation. The auth model (Lexik JWT vs. newer OAuth2), serialization groups, and pagination semantics are materially different. Ask the Sylius team to confirm before you build a client.
  2. Where does the OpenAPI spec live? Sylius exposes /api/v2/docs (JSON-LD/Hydra + OpenAPI). Download that JSON, commit it, and treat it as the contract you version against — not the Composer constraint. Your Laravel connector is tested against the snapshot, not against a Sylius semver.
  3. Read-only or write paths? Catalog, pricing, stock, and order status are read-heavy and low-risk. Cart mutation, checkout, payment, and inventory reservation are the risky surface and demand a different design (idempotency keys, circuit breakers, shadow traffic). Scope the first milestone to reads.

Minimum viable first step on the Laravel side:

composer require saloonphp/saloon spatie/laravel-data
php artisan make:saloon-connector SyliusConnector

Point the connector at https://shop.example.com/api/v2, add a bearer token authenticator, and hit GET /shop/products from tinker to confirm the round-trip. That's your day-one "hello world." The rest of the assessment is about productionizing that call.

Where to look first in the package source: src/Sylius/Bundle/ApiBundle/Resources/config/api_resources/ (the resource definitions and serialization groups that define the contract), Routing/ (how /api/v2/... is generated), and Serializer/ (the custom normalizers). Reading these is how you discover why a field looks the way it does — the OpenAPI spec tells you it's there, the bundle source tells you why.

Implementation Patterns

Pattern 1 — Typed HTTP client, not a raw Guzzle wrapper. Every Sylius resource gets a Saloon connector method and a spatie/laravel-data DTO. This is the difference between a system you can refactor safely and one where every response shape is a stringly-typed array.

class SyliusConnector extends Connector
{
    public function resolveBaseUrl(): string
    {
        return config('services.sylius.base_url'); // e.g. https://shop.example.com/api/v2
    }

    protected function defaultAuth(): Authenticator
    {
        return new BearerAuthenticator(app(SyliusTokenService::class)->current());
    }
}

class GetProductRequest extends Request
{
    public function __construct(private string $code) {}

    public function resolveEndpoint(): string
    {
        return "/shop/products/{$this->code}";
    }
}

final class ProductData extends Data
{
    public function __construct(
        public string $code,
        public string $name,
        public array $translations,
        public PriceData $price,
        public bool $enabled,
    ) {}
}

Pattern 2 — Cache decorator per resource. Catalog and price data change infrequently; don't hit Sylius on every page render. Wrap the connector in a CachedSyliusClient that keys on (resource, id, locale, channel) and uses Cache::remember with tag-based invalidation driven by Sylius webhooks. This is also your stale-cache fallback when Sylius is slow or down — the circuit breaker trips, and reads serve from cache rather than failing.

Pattern 3 — Token acquisition as a dedicated service, never in controllers. Lexik JWT on the Sylius side means you have to acquire and refresh tokens. That service should:

  • Cache the token in Cache::store('redis') with a lease slightly shorter than the JWT ttl.
  • Refresh proactively (90% of TTL) and reactively on a 401 (one retry, then fail hard).
  • Serialize concurrent refreshes with a Lock so a thundering herd under load doesn't trip Sylius' login throttle — the sporadic-401s-under-load failure mode is almost always this.
class SyliusTokenService
{
    public function current(): string
    {
        return Cache::remember('sylius.jwt', now()->addMinutes(50), function () {
            return Http::asForm()->post(config('services.sylius.token_url'), [
                'grant_type' => 'password',
                'username'   => config('services.sylius.username'),
                'password'   => config('services.sylius.password'),
                'client_id'  => config('services.sylius.client_id'),
                'client_secret' => config('services.sylius.client_secret'),
            ])->throw()->json('access_token');
        });
    }
}

Pattern 4 — Hydra pagination is not LengthAwarePaginator. API Platform returns hydra:member + hydra:view with hydra:next as a URL, not ?page=2. Normalize this in the connector so your controllers get a LengthAwarePaginator-shaped result. Filtering uses ?code[in][]=...-style query params generated by API Platform search filters — discover them from the OpenAPI spec, not by guessing.

Pattern 5 — Webhook ingestion as the write-path backlink. Sylius can fire webhooks on order state changes, inventory updates, and customer creation. Ingest these into a queue (php artisan queue:work) with an idempotency key derived from the event ID, and process them with a ->onQueue('sylius-events') job. This is the only durable way to keep your Laravel-side cache and any mirrored data consistent without polling.

Pattern 6 — Dual-run migration. Don't flip catalog reads from your existing source to Sylius in one release. Run both paths behind a feature flag, sample responses with a shadow-traffic middleware, and diff. Cut over domain-by-domain: catalog → pricing → stock → cart → checkout → orders. Checkout last, always.

Pattern 7 — Contract tests on the OpenAPI snapshot. Commit openapi.sylius.json next to your code, generate DTOs from it (or validate hand-written ones against it in CI), and fail the build when the upstream spec drifts. This is the guardrail that turns the "contract drift across upgrades" risk into a CI red build instead of a production incident.

Gotchas and Tips

  • Do not put sylius/api-bundle in your Laravel composer.json. It will resolve, but nothing will autoload because there's no Symfony kernel. The dependency you want is zero — you want an HTTP client and a committed spec.
  • The repo is read-only. Issues and PRs are rejected. Bug fixes require patching Sylius/Sylius upstream or forking the monorepo. Plan the escalation path before you need it.
  • Serialization groups are the real contract, and they're implicit. A field disappearing from a response usually means an upstream serialization group changed, not that the field was deleted. When debugging "where did price go?", check Resources/config/api_resources/Product.xml in the Sylius source for the group attached to that property, then check which groups your request's Accept header / route triggers. The /shop/products and /admin/products endpoints expose different fields from the same entity.
  • Collection reads N+1 hard. Sylius API endpoints have historically serialized relations lazily. Aggressive caching on catalog collections is not optional — first-hit latency can be seconds on a large catalog. Paginate at the connector level (default itemsPerPage=30), never trust a client to send a limit, and cache by page+channel+locale.
  • Hydra IRI references are not IDs. A product response's variant field is "/api/v2/shop/product-variants/123", not 123. Either resolve via a second request (slow) or extract the trailing segment (fragile across Sylius versions — pin it in tests). Prefer code fields where the API exposes them.
  • Auth failure modes present as 401s that look like 403s that look like 500s. Sylius returns 401 for expired tokens, 403 for role denial, and 500 for misconfigured firewalls. Normalize this in the connector's error handling so controllers see a domain exception (SyliusAuthException vs. SyliusClientException vs. SyliusServerException) — the runbook depends on distinguishing them.
  • Cart and order state machine divergence is the top write-path defect. Your Laravel session has its own notion of "cart ID," the Sylius cart has a state machine (cart → addressed → shipping_selected → payment_selected → completed). If you cache a cart ID and the Sylius side expires or transitions it, you'll see 409 Conflict and 422 Unprocessable. Design the cart flow as a single-owner-per-request server-side operation with no client-side state, and treat the Sylius cart as the source of truth — the Laravel side only holds a pointer.
  • Inventory is not transactional across the boundary. Concurrent add-to-cart or checkout across two systems has no shared transaction. Design for optimistic conflict resolution (If-Match / ETag where exposed) or accept a reconciliation job. Don't try to enforce strong consistency — you can't, and pretending you can is where the long-term defects live.
  • Extending beyond core resources. The bundle's extension point is adding API Platform resources in your own Symfony app, or a custom DataProvider/DataPersister on the Sylius side. That's a Symfony job, and it belongs to whoever owns the Sylius deployment — not to the Laravel team. If the Laravel team is being asked to extend the API, that's a scoping signal to escalate.
  • Extension points you can own from Laravel: a resilient connector, a token service, a cache decorator, a webhook ingestor, DTOs, contract tests, a circuit breaker (laravel-fuse or a hand-rolled CircuitBreaker in the connector), and a runbook. This is your entire customization surface. It's small, which is the point.
  • Version-matrix discipline. Pin the Sylius minor version, record the Symfony/PHP/Doctrine/API Platform matrix in docs/sylius.md, and re-verify the OpenAPI snapshot diff on every Sylius bump. The Composer constraint on your Laravel side resolves nothing about the remote service — treat the remote version as a declared runtime dependency, not a Composer one.
  • Two-framework tax is real and permanent. Someone on the team owns the Sylius side. If nobody does, budget that risk explicitly in the roadmap — the integration will be fine until an upgrade, at which point "who fixes this?" becomes an incident.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core