Search by

letkode / data-provider-bundle

letkode.app

Attribute-driven, namespaced data provider registry and endpoint for Symfony applications

Package info

github.com/letkode/data-provider-bundle

Type:symfony-bundle

pkg:composer/letkode/data-provider-bundle

Statistics

Installs: 364

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

2.1.0 2026-11-04 18:39 UTC

This package is auto-updated.

Last update: 2026-11-04 18:39:59 UTC


README

Attribute-driven, namespaced data provider registry and endpoint for Symfony applications.

Allows any service to expose named, aliased method sets under a provider group (a namespace such as form-options, search, reports, etc.), which are then discoverable at runtime through a single REST endpoint. It generalizes the "expose a repository method as an option list" pattern so it can be reused for any read-only, alias-driven data access use case — not just form options.

Installation

composer require letkode/data-provider-bundle

Symfony Flex will register the bundle automatically. If not using Flex, add it manually:

// config/bundles.php
return [
    Letkode\DataProviderBundle\LetkodeDataProviderBundle::class => ['all' => true],
];

Import the routes to expose the endpoint:

# config/routes/letkode_data_provider.yaml
letkode_data_provider:
    resource: .
    type: letkode_data_provider

or let the bundle write that file, plus an example config/packages/letkode_data_provider.yaml, for you:

bin/console letkode:config:publish data-provider

It never overwrites an existing file unless you add --force. --dry-run shows what it would do.

Configuration

The bundle works with no configuration and exposes GET /api/providers/.... To change the URLs, set endpoints as {name: base path}:

# config/packages/letkode_data_provider.yaml
letkode_data_provider:
    endpoints:
        default: /api/providers   # the default; use /api/v1/providers for a versioned API

Each endpoint exposes GET {base}/{providerGroup}/{classAlias}/{methodAlias} as the route letkode_data_provider_{name}, so bin/console debug:router lists them all.

You can declare several, for example one per area of your app:

letkode_data_provider:
    endpoints:
        hub: /api/hub
        tenant: /api/tenant
  • Your endpoints replace the default one, they are not added to it. The example above exposes no /api/providers.
  • endpoints: {} exposes no route at all.
  • An endpoint can also be a map, to turn on its group_map: hub: { path: /api/hub, group_map: true }. That exposes GET {base}/{providerGroup} for that endpoint only (route letkode_data_provider_{name}_group_map). Off by default, see below.
  • route_name_prefix (default letkode_data_provider) sets the prefix of the route names, so route_name_prefix: app_providers yields app_providers_hub.
  • The name accepts letters, numbers and _. The base path gets a leading / and loses the trailing one (api/v1/ becomes /api/v1).

Usage

1. Mark a service as a provider

use Letkode\DataProviderBundle\Attribute\DataProvider;
use Letkode\DataProviderBundle\Attribute\DataProviderMethod;

#[DataProvider(providerGroup: 'form-options', alias: 'countries')]
final class CountryRepository extends ServiceEntityRepository
{
    #[DataProviderMethod(alias: 'active')]
    public function findActiveAsOptions(): array
    {
        return $this->createQueryBuilder('c')
            ->select('c.uuid, c.code, c.name')
            ->where('c.active = true')
            ->getQuery()
            ->getArrayResult();
    }
}

2. Fetch data via the API

GET /api/providers/form-options/countries/active
[
    { "uuid": "...", "code": "CL", "name": "Chile" },
    { "uuid": "...", "code": "AR", "name": "Argentina" }
]

3. Reuse the same mechanism for other domains

The providerGroup namespaces providers so unrelated features can reuse the same alias without colliding:

#[DataProvider(providerGroup: 'search', alias: 'countries')]
final class CountrySearchService
{
    #[DataProviderMethod(alias: 'by-name')]
    public function searchByName(): array { /* ... */ }
}
GET /api/providers/search/countries/by-name?q=chi

4. Discover what is registered

Over HTTP, enable group_map: true on the endpoint and ask a group what it holds:

GET /api/providers/form-options
[
    { "alias": "countries", "methods": ["active"] },
    { "alias": "roles", "methods": ["list"] }
]

Or from the console, which needs no route:

bin/console letkode:data-provider:map            # every group
bin/console letkode:data-provider:map form-options
 ------------ ----------- -------- 
  Group        Provider    Methods  
 ------------ ----------- -------- 
  form-options countries   active   
  form-options roles       list     
 ------------ ----------- -------- 

How it works

  • LetkodeDataProviderBundle::build() calls registerAttributeForAutoconfiguration() so any service annotated with #[DataProvider] is automatically tagged as letkode.data_provider.
  • ProviderRegistry collects all tagged providers via #[AutowireIterator] and indexes them in three levels: providerGroup → classAlias → methodAlias.
  • DataProviderController resolves providerGroup + classAlias + methodAlias and returns the result as JSON. Query parameters, if present, are passed as a single array argument to the resolved method.
  • DataProviderRouteLoader builds one route per entry of letkode_data_provider.endpoints (plus a group map route for each entry with group_map: true), all pointing at DataProviderController.

Requirements

  • PHP ^8.4
  • Symfony ^7.0 || ^8.0

License

MIT — see LICENSE.