Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

State Providers in Detail

State Providers in Detail

~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Block 6 used state providers PRAGMATICALLY, WITHOUT explaining the concept – this chapter catches up FULLY: HOW does API Platform read data AT ALL, and WHY can that be SWAPPED OUT?

The default provider: invisible, but always there

EVERY #[ApiResource] operation WITHOUT an explicit provider attribute INTERNALLY uses a Doctrine-based DEFAULT provider – EXACTLY WHAT has been happening in the background since chapter 9, WITHOUT us ever writing a provider OURSELVES.

The ProviderInterface

api/src/State/ExampleProvider.php
<?php

declare(strict_types=1);

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;

final class ExampleProvider implements ProviderInterface
{
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        // For an item operation: return ONE object (or null).
        // For a collection operation: return an iterable.
        return null;
    }
}

JUST ONE method, provide() – the RETURN value depends on the operation type: Get expects ONE object (or null for 404), GetCollection expects an iterable.

Wrapping vs. replacing

StrategyBehavior
WRAPPING (chapters 48, 54)Injects the DEFAULT provider/processor via the constructor, calls it AFTER its own logic – the DEFAULT flow stays INTACT
REPLACING (chapter 55)Takes over data retrieval ENTIRELY on its own, NO call to the default mechanism

OwnProjectsCollectionProvider from chapter 55 REPLACED the query ENTIRELY (findBy() instead of the default query) – ProjectOwnerProcessor from chapter 54, by contrast, only WRAPPED ADDITIONAL logic AROUND the default flow.

Which strategy when

  • WRAPPING: for ADDITIONAL side effects (hashing, automatically setting fields, logging) – the BASIC flow stays Doctrine-based.
  • REPLACING: when the data source is NOT the database (external API, cache, computed values) OR the query logic STRUCTURALLY differs (chapter 55).

Registering the provider

new Get(provider: ExampleProvider::class),

EXACTLY the same syntax as processor:provider can be set AT the resource level (applies to ALL read operations) OR AT the operation level (ONLY this one).

Tipp: Symfony's autowiring AUTOMATICALLY injects EVERYTHING the provider's constructor REQUIRES (repositories, Security, custom services) – NO manual services.yaml configuration needed, EXACTLY as with voters (chapter 52).