j-ben87/data-bundle
j-ben87/data-bundle is a lightweight Laravel/PHP package for grouping related data into a single reusable bundle. It helps you organize, carry, and share structured values cleanly across controllers, services, and views, keeping application code tidier and easier to maintain.
j-ben87/data-bundle is a Symfony bundle, not a Laravel package. If you are working in a Laravel codebase, the realistic first step is to treat it as a reference implementation for a CLI-driven dataset loader rather than a drop-in dependency — Composer will install it, but the service wiring, command registration and Kernel hooks are all Symfony-specific. In a Symfony app the flow is straightforward:
composer require j-ben87/data-bundle
Register the bundle the way your Symfony version expects (config/bundles.php on Symfony 4+, or AppKernel::registerBundles() on Symfony 3):
// config/bundles.php
return [
// ...
JBen87\DataBundle\DataBundle::class => ['all' => true],
];
Where to look first: the src/Command/ directory (the CLI entry point is the whole point of the bundle) and src/Loader/ (the abstraction you will extend). Read the command class before anything else — with 0 stars and 0 dependents there is effectively no documentation beyond the README, so the command signature and the loader interface are your real API docs.
First use case: reference/lookup data that is not user-generated — countries, currencies, tax rates, feature flags, seed roles — that you want to load identically into dev, staging and prod from a versioned file rather than through Doctrine fixtures (which are dev/test-only by convention).
# typical shape — verify the exact command name against src/Command
bin/console data:load countries
bin/console data:load --list
Because the package targets Symfony ~3/4-era conventions (last release 2018-01-24), expect to spend the first hour reconciling it with your Symfony version before you write a single dataset.
One dataset per bounded set of reference data. Do not build a single all dataset. Name them by domain (countries, currencies, permissions) so a deploy can load only what changed and so failures are isolated.
Make every loader idempotent. This is the pattern that determines whether the bundle is useful or a liability. Loaders should upsert on a natural key (iso_code, slug) rather than blind-insert, so re-running on every deploy is safe:
public function load(): void
{
foreach ($this->readCsv(__DIR__.'/../../data/countries.csv') as $row) {
$this->em->getRepository(Country::class)->upsert($row); // or findOneBy + persist
}
$this->em->flush();
}
Keep source data in the repo, loaders in code. CSV/YAML/JSON under data/, PHP loader classes that know how to map it. This keeps diffs reviewable and lets non-developers submit a PR to change a tax rate.
Wire it into deploy, not into doctrine:fixtures:load. The whole value proposition is that this runs outside the fixtures lifecycle:
# deploy script
bin/console data:load countries --env=prod --no-interaction
bin/console data:load currencies --env=prod --no-interaction
Wrap in a transaction and fail loudly. A half-loaded reference table is worse than none. Assert row counts after load, and exit non-zero so your deploy pipeline halts:
$this->em->wrapInTransaction(function () { /* ... */ });
if ($loaded !== $expected) {
throw new \RuntimeException("Expected {$expected}, loaded {$loaded}");
}
Call the loader directly from tests. The CLI command is a thin wrapper; the loader is your test seam. Seed your test database by invoking the loader service against the test entity manager instead of shelling out to the console — vastly faster and easier to assert on.
Laravel bridge pattern. If you genuinely want this in Laravel, do not fight the bundle. Extract the loader contract (roughly a load(): void interface plus a registry) into your own app/DataLoaders namespace, register them in a service provider with tags, and expose a single Artisan command:
class LoadDatasetCommand extends Command
{
protected $signature = 'data:load {dataset}';
public function handle(LoaderRegistry $registry): int
{
$registry->get($this->argument('dataset'))->load();
return self::SUCCESS;
}
}
You get the same day-to-day ergonomics without inheriting a 2018 Symfony bundle's container assumptions.
It is effectively unmaintained. Last release January 2018, zero stars, zero dependents. There is no one to file issues against in any meaningful way. Budget for reading and patching the source yourself, and check the composer.json constraints before installing — you will likely need --ignore-platform-reqs or a fork to get it onto modern Symfony/PHP.
Auto-registration of commands is version-dependent. Older bundles often rely on services.yml with explicit tags: [console.command] rather than autoconfigure. If your command does not appear in bin/console list, that is the first thing to check — not your loader code.
Duplicate-key errors on second run are the number-one symptom of a non-idempotent loader. If data:load works once and explodes on redeploy, the fix is in your loader (upsert), not the bundle.
Large datasets and the Doctrine identity map. Loading tens of thousands of rows through the ORM will bloat memory. Batch every few hundred records with flush() + clear(), or drop to Connection::executeUpdate for pure lookup tables — they rarely need entities.
Environment leakage. There is no guardrail stopping data:load demo-users from running against prod. Gate destructive or environment-specific datasets behind an explicit --force or a dataset-level environments: declaration you enforce in your own wrapper.
Extension point is the loader registry. Most versions of this style of bundle expose a tagged service or a config map (data.datasets) to register loaders. Read DependencyInjection/Configuration.php and the extension class — that file is the authoritative list of what you can configure, and it is shorter than you expect.
Debug with -vvv and the container. bin/console debug:container --tag=console.command (or the bundle's loader tag) confirms registration; bin/console data:load foo -vvv will usually surface the real exception behind a swallowed, user-friendly console message.
Do not let this become your fixtures story. It is a dataset loader for stable reference data. Test fixtures, faker-generated users and per-environment demo content belong in doctrine/data-fixtures or your framework's seeder layer. Conflating the two is how teams end up with a "seed the database" command nobody trusts.
How can I help you explore Laravel packages today?