1. Go to this page and download the library: Download c975l/config-bundle library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
c975l / config-bundle example snippets
// src/Entity/User.php
use c975L\ConfigBundle\Contract\UserInterface;
class User implements UserInterface
{
// ...
}
use c975L\ConfigBundle\Service\Export\ExportFormat;
use c975L\ConfigBundle\Service\Export\TableExporter;
use Doctrine\DBAL\Connection;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\ActionGroup;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use Symfony\Component\HttpFoundation\Response;
class MyEntityCrudController extends AbstractCrudController
{
public function __construct(
private readonly Connection $connection,
private readonly TableExporter $tableExporter,
) {}
public function configureActions(Actions $actions): Actions
{
$exportGroup = ActionGroup::new('export', 'Export', 'fa fa-download')
->createAsGlobalActionGroup()
->addAction(Action::new('exportSql', 'SQL')->linkToCrudAction('exportSql'))
->addAction(Action::new('exportCsv', 'CSV')->linkToCrudAction('exportCsv'))
->addAction(Action::new('exportJson', 'JSON')->linkToCrudAction('exportJson'))
;
return $actions->add(Crud::PAGE_INDEX, $exportGroup);
}
#[AdminRoute]
public function exportSql(AdminContext $context): Response
{
// Set 'primary_key' to enable ON DUPLICATE KEY UPDATE; omit it for a plain INSERT-only dump
return $this->tableExporter->export(ExportFormat::Sql, 'my_table', $this->fetchRows());
}
#[AdminRoute]
public function exportCsv(AdminContext $context): Response
{
return $this->tableExporter->export(ExportFormat::Csv, 'my_table', $this->fetchRows());
}
#[AdminRoute]
public function exportJson(AdminContext $context): Response
{
return $this->tableExporter->export(ExportFormat::Json, 'my_table', $this->fetchRows());
}
private function fetchRows(): array
{
return $this->connection->fetchAllAssociative('SELECT * FROM `my_table`');
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\ImportProviderInterface;
class MyImportProvider implements ImportProviderInterface
{
// $kind is the string embedded in the export payload (see ContentExporter::export()), stable across dev/prod (e.g. "site_page")
public function supportsImport(string $kind): bool
{
return 'my_entity' === $kind;
}
// $items are the payload's raw "items" array, one entry per exported entity. $filesDir is the directory the export's zip was extracted into — any 'file' reference inside $items is relative to it, null for a kind that never carries files. Match by a natural key (slug/name...), never a raw id: dev and prod ids never need to match. Returns ['created' => int, 'updated' => int]
public function import(array $items, ?string $filesDir = null): array
{
// ...
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\ExportProviderInterface;
class MyExportProvider implements ExportProviderInterface
{
// The string embedded in the export payload for this provider's items (see ContentExporter), stable across dev/prod (e.g. "my_entity")
public function getKind(): string
{
return 'my_entity';
}
// Same shapes ContentExporter::export() expects: 'items' (JSON-able array, one entry per exported entity) and 'files' (archive-relative path => disk path, empty for a kind that never carries files)
public function exportAll(): array
{
return ['items' => $this->fetchItems(), 'files' => []];
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\MenuProviderInterface;
use c975L\MyBundle\Controller\Management\MyCrudController;
class MenuProvider implements MenuProviderInterface
{
public function getMenuSection(): array
{
return [
'label' => 'label.my_section',
'translation_domain' => 'my_bundle',
];
}
public function getMenus(): array
{
return [
'my_entity' => [
'controller' => MyCrudController::class,
'label' => 'label.my_entity',
'translation_domain' => 'my_bundle',
'icon' => 'fas fa-star',
],
];
}
// Links to plain routes (not EasyAdmin CRUD controllers); return [] if none
public function getLinks(): array
{
return [];
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\LinkableRouteProviderInterface;
class LinkableRouteProvider implements LinkableRouteProviderInterface
{
// Route name => ['label' => translation key, 'translation_domain' => domain]; return [] if none
public function getLinkableRoutes(): array
{
return [
'my_bundle_display' => [
'label' => 'label.my_page',
'translation_domain' => 'my_bundle',
],
];
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\ImportmapProviderInterface;
class ImportmapProvider implements ImportmapProviderInterface
{
// Import name => ['path' => string, 'entrypoint' => bool]. 'path' is relative to the project root, exactly as it should appear in importmap.php
public function getAdminImportmapEntries(): array
{
return [
'@c975l/my-bundle/controllers-admin.js' => [
'path' => './vendor/c975l/my-bundle/assets/controllers-admin.js',
'entrypoint' => true,
],
];
}
public function getImportmapEntries(): array
{
return [];
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\SitemapProviderInterface;
class MySitemapProvider implements SitemapProviderInterface
{
// Gives public/sitemap-my-bundle.xml - keep it short and stable, it ends up in a public url
public function getSitemapName(): string
{
return 'my-bundle';
}
public function getUrls(): array
{
return [[
'loc' => 'https://example.com/my-thing/some-slug',
'lastmod' => '2026-07-26',
'changefreq' => 'monthly',
'priority' => 8,
]];
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\WhatsNewJsonReader;
use c975L\ConfigBundle\Management\WhatsNewProviderInterface;
class WhatsNewProvider implements WhatsNewProviderInterface
{
public function getEntries(): array
{
return WhatsNewJsonReader::read(\dirname(__DIR__, 2) . '/config/whatsnew.json');
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Entity\Config;
use c975L\ConfigBundle\Management\AlertProviderInterface;
class MyAlertProvider implements AlertProviderInterface
{
public function getAlerts(): array
{
return [
[
'label' => 'My entity label',
'description' => 'Why it needs attention',
'severity' => Config::SEVERITY_WARNING,
'url' => '/management/my-entity/edit/1',
],
];
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\ShortcutProviderInterface;
use c975L\MyBundle\Controller\Management\MyShortcutController;
use Symfony\Contracts\Translation\TranslatorInterface;
class MyShortcutProvider implements ShortcutProviderInterface
{
public function __construct(
private readonly TranslatorInterface $translator,
) {
}
public function getShortcuts(): array
{
return [
[
'label' => $this->translator->trans('label.toggle_maintenance', [], 'my_bundle'),
'icon' => 'fas fa-wrench',
'route' => MyShortcutController::TOGGLE_MAINTENANCE_ROUTE,
'active' => $this->isMaintenanceOn(),
'role' => 'ROLE_SUPER_ADMIN',
'category' => ShortcutProviderInterface::CATEGORY_MAINTENANCE,
],
];
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\DashboardWidgetProviderInterface;
class MyDashboardWidgetProvider implements DashboardWidgetProviderInterface
{
public function getDashboardWidgets(): array
{
if (!$this->isEnabled()) {
return [];
}
return [
['template' => '@MyBundle/management/_my_widget.html.twig', 'context' => ['foo' => 'bar']],
];
}
}
namespace c975L\MyBundle\Tests\Management;
use c975L\ConfigBundle\Test\ManagementTargetsTestCase;
use c975L\MyBundle\Management\MenuProvider;
use c975L\MyBundle\Management\MyGuidedProjectProvider;
class ManagementTargetsTest extends ManagementTargetsTestCase
{
protected function managementProviders(): iterable
{
return [
new MenuProvider($this->createStub(ConfigServiceInterface::class)),
// A provider generating urls takes these two recorders, so the targets behind its urls can be read back
new MyGuidedProjectProvider($this->adminUrlGenerator(), $this->urlGenerator()),
];
}
// ConfigBundle's own controllers are watched by default (every bundle links to its screens); add yours
protected function controllerDirectories(): array
{
return [...parent::controllerDirectories(), __DIR__ . '/../../src/Controller'];
}
}
->add($this->spreader->spread('# */6 * * *', new RunCommandMessage('c975l:config:backup')))
->add($this->spreader->spread('# #(2-5) * * 1', new RunCommandMessage('c975l:config:backup:digest')))
use c975L\ConfigBundle\Scheduler\ScheduleSpreader;
public function __construct(
private readonly ScheduleSpreader $spreader,
private readonly CacheInterface $cache,
) {
}
public function getSchedule(): Schedule
{
return (new Schedule())
->stateful($this->cache)
->add($this->spreader->spread('# #(0-2) * * *', new RunCommandMessage('c975l:sitemaps:create')))
->add($this->spreader->spread('# */6 * * *', new RunCommandMessage('c975l:config:backup')))
;
}
namespace c975L\ShopBundle\Scheduler;
use c975L\ConfigBundle\Scheduler\MaintenanceTask;
use c975L\ConfigBundle\Scheduler\MaintenanceTaskProviderInterface;
class ShopMaintenanceTaskProvider implements MaintenanceTaskProviderInterface
{
public function getMaintenanceTasks(): array
{
return [
// Expired download links, nightly
new MaintenanceTask('# #(1-3) * * *', 'c975l:shop:downloads:delete'),
// Product affinities, monthly: a full pass over the orders, too long to run nightly for what it changes
new MaintenanceTask('# #(2-5) # * *', 'c975l:shop:affinity:calculate'),
];
}
}
// src/Scheduler/MaintenanceSchedule.php, scaffolded by c975l/site-bundle
return $this->builder->addTasks((new Schedule())->stateful($this->cache));
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Entity\HealthCheckResult;
use c975L\ConfigBundle\Management\HealthCheckProviderInterface;
class MyHealthCheckProvider implements HealthCheckProviderInterface
{
// Stable identifier for this provider's rows (eg. "my-check") - used for --kind= filtering and stored on every HealthCheckResult
public function getKind(): string
{
return 'my-check';
}
// One entry per checked url: ['url', 'label', 'status' => HealthCheckResult::STATUS_*, 'summary', 'details' => array, 'editUrl']
public function runChecks(): array
{
return [
[
'url' => 'https://example.com/pages/home/',
'label' => 'Home',
'status' => HealthCheckResult::STATUS_OK,
'summary' => 'Everything checks out',
'details' => null,
'editUrl' => '/management/my-entity/1/edit',
],
];
}
}
use c975L\ConfigBundle\Attribute\AsHealthCheck;
// A run holding thousands of urls has no business on the same schedule as a handful of pages
#[AsHealthCheck(frequency: AsHealthCheck::FREQUENCY_MONTHLY)]
class MyHeavyHealthCheckProvider implements HealthCheckProviderInterface
class MyGeneratedHealthCheckProvider implements HealthCheckProviderInterface, HealthCheckFrequencyAwareInterface
{
public function __construct(private readonly string $frequency = AsHealthCheck::FREQUENCY_WEEKLY)
{
}
public function getFrequency(): string
{
return $this->frequency;
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Entity\HealthCheckResult;
use c975L\ConfigBundle\Management\HealthCheckAdviceBuilder;
use c975L\ConfigBundle\Management\HealthCheckAdviceProviderInterface;
class MyHealthCheckAdviceProvider implements HealthCheckAdviceProviderInterface
{
// Keyed per result, via HealthCheckAdviceBuilder::key() (only the results this provider actually has something to say about) - $results is the same HealthCheckResult[] the current screen renders (dashboard "Health check" page or a CRUD's own scoped tab)
public function buildAdvice(array $results): array
{
$advice = [];
foreach ($results as $result) {
if ('my-check' !== $result->getKind()) {
continue;
}
$advice[HealthCheckAdviceBuilder::key($result)] = [
[
'text' => '3 images are missing an alt text',
'url' => '/management/my-entity/1/edit',
// Optional - the individual offenders behind that line, rendered as a collapsed list under it
'items' => [
['text' => 'banner.jpg', 'url' => '/management/my-entity/1/edit#block-4', 'label' => 'Edit the block'],
],
],
];
}
return $advice;
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\StatusProviderInterface;
class MyStatusProvider implements StatusProviderInterface
{
// The key this provider occupies in the report's "extra" section
public function getStatusKey(): string
{
return 'shop';
}
// Counts and dates, nothing else: it travels over the network, and a receiver has no way to know a key is confidential
public function getStatusData(): array
{
return [
'pendingOrders' => $this->orderRepository->countPending(),
'lastOrderAt' => $this->orderRepository->findLastDate()?->format(\DateTimeInterface::ATOM),
];
}
}
namespace App\Management;
use c975L\ConfigBundle\Management\DevProfilePathProviderInterface;
use Symfony\Component\DependencyInjection\Attribute\When;
#[When('dev')]
class MyDevProfilePathProvider implements DevProfilePathProviderInterface
{
public function __construct(
private readonly MyRepository $myRepository,
) {
}
// One entry per path to profile: ['path' => local absolute path, 'label' => ?string]
public function getPaths(): array
{
$paths = [];
foreach ($this->myRepository->findAllPublished() as $item) {
$paths[] = ['path' => '/shop/' . $item->getSlug(), 'label' => $item->getName()];
}
return $paths;
}
}
namespace c975L\MyBundle\Management;
use c975L\ConfigBundle\Management\ProcedureJsonReader;
use c975L\ConfigBundle\Management\ProcedureProviderInterface;
class MyProcedureProvider implements ProcedureProviderInterface
{
public function getProcedures(): array
{
return ProcedureJsonReader::read(\dirname(__DIR__, 2) . '/config/procedures.json');
}
}
use c975L\ConfigBundle\Service\ConfigServiceInterface;
class MyService
{
public function __construct(
private readonly ConfigServiceInterface $configService,
) {}
public function doSomething(): void
{
$siteName = $this->configService->get('site-name'); // string
$maxItems = $this->configService->get('max-items'); // int (auto-cast)
$isEnabled = $this->configService->get('feature-enabled'); // bool (auto-cast)
$env = $this->configService->getContainerParameter('kernel.environment');
}
}