letkode / data-provider-bundle
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
Requires
- php: ^8.4
- letkode/config-publisher-bundle: ^1.0
- symfony/config: ^7.0 || ^8.0
- symfony/console: ^7.0 || ^8.0
- symfony/dependency-injection: ^7.0 || ^8.0
- symfony/framework-bundle: ^7.0 || ^8.0
- symfony/routing: ^7.0 || ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- phpstan/phpstan: ^2
- phpunit/phpunit: ^11
- symfony/yaml: ^7.0 || ^8.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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
endpointsreplace 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 exposesGET {base}/{providerGroup}for that endpoint only (routeletkode_data_provider_{name}_group_map). Off by default, see below. route_name_prefix(defaultletkode_data_provider) sets the prefix of the route names, soroute_name_prefix: app_providersyieldsapp_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()callsregisterAttributeForAutoconfiguration()so any service annotated with#[DataProvider]is automatically tagged asletkode.data_provider.ProviderRegistrycollects all tagged providers via#[AutowireIterator]and indexes them in three levels:providerGroup → classAlias → methodAlias.DataProviderControllerresolvesproviderGroup+classAlias+methodAliasand returns the result as JSON. Query parameters, if present, are passed as a single array argument to the resolved method.DataProviderRouteLoaderbuilds one route per entry ofletkode_data_provider.endpoints(plus a group map route for each entry withgroup_map: true), all pointing atDataProviderController.
Requirements
- PHP
^8.4 - Symfony
^7.0 || ^8.0
License
MIT — see LICENSE.