Platform Parameter Bundle is a Symfony bundle for managing global platform parameters stored in the database with type-safe access and caching system.
php artisan boost:add-skill ecourty/platform-parameter-bundle
Save this content to: AGENTS.md
# AGENTS.md - Platform Parameter Bundle
## ๐ฏ Core Concept
**Platform Parameter Bundle** is a Symfony bundle for managing **global platform parameters** stored in the database with type-safe access and caching system.
### Problem Solved
- Avoid hard-coded values in code
- Enable dynamic configuration without redeployment
- Centralize business parameters (e.g., limits, feature flags, external URLs)
- Provide a type-safe API to prevent type errors
### Solution
A simple and robust system: **Doctrine Entity + Service Provider + PSR-6 Cache**
---
## ๐๏ธ Architecture
### Overview
The architecture follows a layered approach:
**Application Layer** (Controllers, Services, Commands) interacts with the **PlatformParameterProvider** service through dependency injection. This provider implements the `PlatformParameterProviderInterface` and acts as the single entry point for parameter access.
The provider coordinates three underlying components:
- **PSR-6 Cache**: First line of defense for fast parameter retrieval
- **Doctrine ORM**: Database persistence layer for parameter storage
- **Validator Logic**: Type conversion and validation mechanisms
All parameters are stored in the `platform_parameter` database table and accessed through the ORM.
### Main Components
#### 1. **AbstractPlatformParameter** (Entity)
- **MappedSuperclass** allowing extension
- Fields: `id`, `key`, `value`, `type`, `label`, `description`, timestamps
- Auto-generated UUID
- Lifecycle callbacks for `updatedAt`
```php
// src/Entity/AbstractPlatformParameter.php
#[ORM\MappedSuperclass]
abstract class AbstractPlatformParameter
{
private Uuid $id;
private string $key; // Unique identifier
private string $value; // Stored as TEXT
private ParameterType $type;
private string $label;
private ?string $description;
private \DateTimeImmutable $createdAt;
private \DateTimeImmutable $updatedAt;
}
```
**Design Choice**: `MappedSuperclass` allows projects to extend the entity to add custom fields (category, icon, sortOrder, etc.)
#### 2. **PlatformParameter** (Concrete Entity)
- Inherits from `AbstractPlatformParameter`
- Standard Doctrine entity used by default
- Table `platform_parameter`
```php
#[ORM\Entity]
#[ORM\Table(name: 'platform_parameter')]
class PlatformParameter extends AbstractPlatformParameter
{
// Ready to use, no custom fields
}
```
#### 3. **ParameterType** (Enum)
7 supported types with built-in validation:
```php
enum ParameterType: string
{
case STRING = 'string'; // Simple text
case INTEGER = 'integer'; // Integer number
case BOOLEAN = 'boolean'; // true/false/1/0/yes/no
case JSON = 'json'; // JSON object/array
case LIST = 'list'; // Multi-line or delimiter-separated
case FLOAT = 'float'; // Decimal number
case DATETIME = 'datetime'; // Date/time with multiple formats
}
```
#### 4. **PlatformParameterProviderInterface** (Contract)
Service interface exposing the public API:
```php
interface PlatformParameterProviderInterface
{
public function getString(string $key, ?string $default = null): string;
public function getInt(string $key, ?int $default = null): int;
public function getBool(string $key, ?bool $default = null): bool;
public function getJson(string $key, ?array $default = null): array;
public function getList(string $key, ?array $default = null, string $separator = "\n"): array;
public function getFloat(string $key, ?float $default = null): float;
public function getDateTime(string $key, ?\DateTimeImmutable $default = null, ?string $format = null): \DateTimeImmutable;
public function has(string $key): bool;
public function clearCache(?string $key = null): void;
}
```
**Design Pattern**: Interface injection for testability and decoupling
#### 5. **PlatformParameterProvider** (Service - READ)
Provider implementation with business logic:
**Responsibilities**:
- Fetch from cache or DB
- Type validation and conversion
- Error handling (exceptions or defaults)
- Smart cache invalidation
**Execution Flow**:
When a parameter is requested, the provider follows this sequence:
1. **Cache Check**: Look for the parameter in cache using the format `{prefix}.{key}`
2. **Cache HIT**: If found in cache, return the cached value immediately
3. **Cache MISS**: If not in cache, query the database using Doctrine EntityManager
4. **Database Result**: If found in DB, store it in cache and return the value
5. **Not Found**: If the parameter doesn't exist, either return the provided default value or throw a `ParameterNotFoundException`
#### 6. **PlatformParameterWriter** (Service - WRITE)
Writer service for updating parameter values from code:
**Responsibilities**:
- Update existing parameter values with type safety
- Delete parameters by key
- Type validation (must match parameter's declared type)
- Value conversion (PHP type โ string for database)
- Exception handling (not found or type mismatch)
**Available Methods**:
```php
interface PlatformParameterWriterInterface
{
public function setString(string $key, string $value): void;
public function setInt(string $key, int $value): void;
public function setBool(string $key, bool $value): void;
public function setJson(string $key, array $value): void;
public function setList(string $key, array $value, string $separator = "\n"): void;
public function setFloat(string $key, float $value): void;
public function setDateTime(string $key, \DateTimeImmutable $value, ?string $format = null): void;
public function delete(string $key): void;
}
```
**Behavior**:
- All `set*()` methods throw `ParameterNotFoundException` if the parameter doesn't exist
- All `set*()` methods throw `ParameterTypeMismatchException` if the parameter type doesn't match the method type
- `delete()` method throws `ParameterNotFoundException` if the parameter doesn't exist
- Cache clearing is automatic via Doctrine listener
- Events (`PlatformParameterUpdatedEvent`, `PlatformParameterDeletedEvent`) are dispatched automatically
- **No auto-creation**: Writer only updates existing parameters (creation must be done via Doctrine entities)
**Design Principle**: CQRS-style separation: Provider = READ, Writer = WRITE
#### 7. **Symfony Configuration**
```yaml
# config/packages/platform_parameter.yaml
platform_parameter:
entity_class: 'Ecourty\PlatformParameterBundle\Entity\PlatformParameter'
cache_ttl: 3600
cache_key_prefix: 'platform_parameter'
clear_cache_on_parameter_update: true # Auto-clear cache via Doctrine listener
cache_adapter: null # Optional custom cache service
```
---
## ๐ Data Flow
### Reading a Parameter
When reading a parameter, for example `$provider->getInt('max_uploads', 10)`, the system follows these steps:
1. **Key Generation**: The provider generates a cache key by prefixing the parameter key (e.g., `'platform_parameter.max_uploads'`)
2. **Cache Lookup**: The PSR-6 cache is queried with this key
3. **Cache Hit Path**: If the item exists in cache, the cached `AbstractPlatformParameter` object is returned directly
4. **Cache Miss Path**: If not in cache, Doctrine's EntityManager performs a `findOneBy(['key' => 'max_uploads'])` query
5. **Database Hit**: If found in the database, the parameter is cached for future requests and the value is returned
6. **Database Miss**: If not found in the database, the default value (10) is returned, or a `ParameterNotFoundException` is thrown if no default was provided
### Writing/Updating Parameters
#### Method 1: PlatformParameterWriter (Recommended for Updates)
The bundle provides a **PlatformParameterWriter** service for type-safe parameter updates from code:
```php
// In a service or controller
public function __construct(
private PlatformParameterWriterInterface $writer,
) {
}
public function updateParameter(): void
{
// Update existing parameters with type-safe methods
$this->writer->setInt('max_uploads', 100);
$this->writer->setBool('maintenance_mode', true);
$this->writer->setString('site_name', 'My Site');
$this->writer->setJson('config', ['key' => 'value']);
$this->writer->setList('emails', ['[email protected]', '[email protected]']);
$this->writer->setFloat('rate', 3.14);
$this->writer->setDateTime('last_sync', new \DateTimeImmutable());
// Delete a parameter
$this->writer->delete('old_parameter');
}
```
**Important**: The Writer can only **update existing parameters**. It throws `ParameterNotFoundException` if the parameter doesn't exist, and `ParameterTypeMismatchException` if you try to set a value with the wrong type (e.g., calling `setInt()` on a STRING parameter).
**Why no auto-creation?** Because if you extend `AbstractPlatformParameter` with custom fields (category, icon, etc.), the Writer doesn't know how to populate those fields. Parameter creation must be done explicitly via Doctrine entities.
#### Method 2: Direct Doctrine Entity Manipulation (For Creation)
To **create** a parameter, you must work directly with Doctrine entities:
```php
// In a service or controller
$parameter = new PlatformParameter();
$parameter->setKey('max_uploads');
$parameter->setValue('20');
$parameter->setType(ParameterType::INTEGER);
$parameter->setLabel('Maximum Uploads');
$parameter->setDescription('Maximum number of files per upload');
$em->persist($parameter);
$em->flush();
```
**By default**, cache is automatically cleared via a Doctrine listener when parameters are created, updated, or deleted. You can disable this behavior by setting `clear_cache_on_parameter_update: false` in configuration if you prefer manual cache management.
### Event System
The bundle dispatches **Symfony events** whenever parameters are created, updated, or deleted. This allows host projects to easily react to parameter changes without configuring Doctrine listeners.
**Available Events**:
1. **PlatformParameterCreatedEvent** - Dispatched after a parameter is created
2. **PlatformParameterUpdatedEvent** - Dispatched after a parameter is updated (includes old and new values)
3. **PlatformParameterDeletedEvent** - Dispatched after a parameter is deleted
**Example: React to parameter changes**
```php
// src/EventSubscriber/ParameterChangeSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Ecourty\PlatformParameterBundle\Event\PlatformParameterUpdatedEvent;
use Ecourty\PlatformParameterBundle\Event\PlatformParameterCreatedEvent;
use Ecourty\PlatformParameterBundle\Event\PlatformParameterDeletedEvent;
class ParameterChangeSubscriber implements EventSubscriberInterface
{
public function __construct(
private SlackNotifier $slack,
private LoggerInterface $logger,
) {
}
public static function getSubscribedEvents(): array
{
return [
PlatformParameterCreatedEvent::class => 'onParameterCreated',
PlatformParameterUpdatedEvent::class => 'onParameterUpdated',
PlatformParameterDeletedEvent::class => 'onParameterDeleted',
];
}
public function onParameterCreated(PlatformParameterCreatedEvent $event): void
{
$parameter = $event->getParameter();
$this->logger->info('Parameter created', [
'key' => $parameter->getKey(),
'value' => $parameter->getValue(),
]);
}
public function onParameterUpdated(PlatformParameterUpdatedEvent $event): void
{
$parameter = $event->getParameter();
// Send Slack notification for critical parameters
if ($parameter->getKey() === 'maintenance_mode') {
$this->slack->send(sprintf(
'Maintenance mode changed from "%s" to "%s"',
$event->getOldValue(),
$event->getNewValue()
));
}
}
public function onParameterDeleted(PlatformParameterDeletedEvent $event): void
{
$parameter = $event->getParameter();
$this->logger->warning('Parameter deleted', [
'key' => $parameter->getKey(),
'last_value' => $parameter->getValue(),
]);
}
}
```
**Event Details**:
- **PlatformParameterCreatedEvent**: Contains the newly created parameter entity
- **PlatformParameterUpdatedEvent**: Contains the parameter entity, old value (string), and new value (string)
- **PlatformParameterDeletedEvent**: Contains the parameter entity (before deletion)
**Events are always dispatched**, regardless of the `clear_cache_on_parameter_update` configuration setting. They work with:
- Console commands (`platform-parameter:set`, `platform-parameter:delete`)
- Direct Doctrine operations (`$em->persist()`, `$em->flush()`, `$em->remove()`)
- EasyAdmin CRUD operations
**Cache Management**: By default, cache is automatically cleared when parameters change. You can disable this behavior by setting `clear_cache_on_parameter_update: false` in configuration, but events will still be dispatched, allowing you to implement custom cache invalidation logic via event subscribers.
---
## ๐ก Design Patterns Used
### 1. **Provider Pattern**
The `PlatformParameterProvider` acts as a uniform facade for retrieving parameters.
### 2. **Type Safety**
Each method returns a strict PHP 8.3 type, preventing type errors.
### 3. **Fail-Safe with Defaults**
```php
$value = $provider->getInt('unknown_key', 100); // Returns 100 if not found
$value = $provider->getInt('unknown_key'); // Throws ParameterNotFoundException
```
### 4. **Cache-Aside (Lazy Loading)**
The cache is checked first, the DB is only consulted in case of MISS.
### 5. **Tagged Cache Support**
If `TagAwareCacheInterface` is available, the bundle uses tags for efficient invalidation.
```php
// Clear all parameters
$provider->clearCache();
// With TagAwareCache: 1 operation
โ invalidateTags(['platform_parameter'])
// Without TagAwareCache: N operations (fetch all + delete each)
โ findAll() โ deleteItem() for each key
```
### 6. **Template Method (Extension Point)**
`AbstractPlatformParameter` allows inheritance for extension without modifying the core.
---
## ๐ Typical Use Cases
### 1. Feature Flags
```php
if ($provider->getBool('feature_new_dashboard', false)) {
return $this->render('dashboard_v2.html.twig');
}
```
### 2. Dynamic Limits
```php
$maxFileSize = $provider->getInt('max_upload_size_mb', 10) * 1024 * 1024;
if ($file->getSize() > $maxFileSize) {
throw new UploadException('File too large');
}
```
### 3. External Configuration
```php
$apiConfig = $provider->getJson('stripe_config', [
'public_key' => '',
'secret_key' => '',
'webhook_url' => '',
]);
$stripe = new StripeClient($apiConfig['secret_key']);
```
### 4. Dynamic Lists
```php
$allowedEmails = $provider->getList('beta_testers_emails', []);
if (!in_array($user->getEmail(), $allowedEmails)) {
throw new AccessDeniedException();
}
```
### 5. Maintenance Mode
```php
// In an EventSubscriber
if ($provider->getBool('maintenance_mode', false)) {
throw new ServiceUnavailableHttpException(3600, 'Under maintenance');
}
```
---
## ๐งฉ Extension: Custom Entity
### Why Extend?
To add custom metadata: category, sort order, icon, tags, etc.
### How?
**Step 1**: Create a custom entity
```php
namespace App\Entity;
#[ORM\Entity]
#[ORM\Table(name: 'platform_parameter')]
class CustomPlatformParameter extends AbstractPlatformParameter
{
#[ORM\Column(type: 'string', length: 100, nullable: true)]
private ?string $category = null;
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $sortOrder = null;
// Getters/setters...
}
```
**Step 2**: Configure the bundle
```yaml
platform_parameter:
entity_class: 'App\Entity\CustomPlatformParameter'
```
**Step 3**: Migration
```bash
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
```
โ
**The provider continues to work as is** - autowiring automatically uses your custom entity.
---
## ๐จ EasyAdmin Integration (Optional)
The bundle provides a **ready-to-use CRUD controller** if EasyAdmin is installed.
### Usage
```php
// src/Controller/Admin/PlatformParameterCrudController.php
namespace App\Controller\Admin;
use Ecourty\PlatformParameterBundle\Controller\PlatformParameterCrudController as BaseCrudController;
class PlatformParameterCrudController extends BaseCrudController
{
// Ready to use!
}
```
### Included Features
- Complete CRUD with validation
- Colored badges for types
- Help text on each field
- Role-based permissions
- Truncated values in list view
---
## โก Performance & Caching
### Cache Strategy
1. **Cache Key Format**: `{prefix}.{parameter_key}` (e.g., `platform_parameter.max_uploads`)
2. **Configurable TTL**: Default 3600s (1h)
3. **Per-parameter cache**: Each key is cached individually
4. **Granular invalidation**: Clear by key or globally
### Optimizations
#### Using TagAwareCache
```yaml
# config/packages/cache.yaml
framework:
cache:
pools:
platform_parameter.cache:
adapter: cache.adapter.redis.tag_aware
default_lifetime: 3600
```
Benefits:
- Bulk invalidation in O(1) instead of O(n)
- Avoids fetching all parameters during `clearCache()`
#### Cache Warmup (Optional)
If you have many critical parameters:
```php
// Command or EventListener
foreach ($criticalKeys as $key) {
try {
$provider->getString($key);
} catch (ParameterNotFoundException) {
// Log missing parameter
}
}
```
---
## ๐ก๏ธ Error Handling
### Exceptions
#### ParameterNotFoundException
Thrown when a parameter doesn't exist and no default is provided.
```php
try {
$value = $provider->getString('missing_key');
} catch (ParameterNotFoundException $e) {
// Log or handle
$value = 'fallback';
}
```
#### InvalidArgumentException
Thrown during type conversion if the value is invalid.
```php
// DB value: "not_a_number"
$provider->getInt('bad_value'); // throws InvalidArgumentException
```
### Best Practices
1. **Always provide a default in production**
```php
// โ Risky
$limit = $provider->getInt('rate_limit');
// โ
Safe
$limit = $provider->getInt('rate_limit', 100);
```
2. **Validate in dev, default in prod**
```php
if ($this->environment === 'dev' && !$provider->has('critical_param')) {
throw new \RuntimeException('Missing critical parameter');
}
$value = $provider->getString('critical_param', 'default');
```
---
## ๐งช Testing
### Mocking the Provider
```php
use Ecourty\PlatformParameterBundle\Contract\PlatformParameterProviderInterface;
class MyServiceTest extends TestCase
{
public function testWithMockProvider(): void
{
$provider = $this->createMock(PlatformParameterProviderInterface::class);
$provider->method('getInt')
->willReturnMap([
['max_uploads', null, 20],
['timeout', null, 30],
]);
$service = new MyService($provider);
// Test service behavior
}
}
```
### Functional Tests
```php
// Create test parameters
$parameter = new PlatformParameter();
$parameter->setKey('test_key');
$parameter->setValue('42');
$parameter->setType(ParameterType::INTEGER);
$this->entityManager->persist($parameter);
$this->entityManager->flush();
// Test provider
$result = $this->parameterProvider->getInt('test_key');
$this->assertSame(42, $result);
```
---
## ๐ Developer Checklist
### Adding a New Parameter
1. โ
Create the entity in DB (manually or via EasyAdmin)
2. โ
Define: key, value, type, label, description
3. โ
Test access via provider
4. โ
Document the parameter in the project (README, confluence, etc.)
### Modifying a Parameter
1. โ
Update in DB via EasyAdmin or SQL
2. โ
**Clear the cache**: `$provider->clearCache('key')` or via console
3. โ
Verify that the new value is active
### Debugging an Issue
1. โ Does the parameter exist in DB? โ `SELECT * FROM platform_parameter WHERE key = '...'`
2. โ Is the cache stale? โ `$provider->clearCache('key')` or `php bin/console platform-parameter:cache:clear key`
3. โ Is the type correct? โ Check `type` column and called method
4. โ Is there a silent exception? โ Check logs
---
## ๐๏ธ CLI Commands
The bundle provides 5 Symfony commands for managing parameters via the console.
### 1. List all parameters
```bash
php bin/console platform-parameter:list
```
**Features**:
- Displays a formatted table with columns: Key, Value, Type, Label
- Values longer than 50 characters are truncated with "..."
- Shows a warning if no parameters exist
- Output: "Found X parameter(s)."
**Use case**: Quick overview of all platform parameters
---
### 2. Get parameter details
```bash
php bin/console platform-parameter:get <key>
```
**Arguments**:
- `key` (required): The parameter key to display
**Features**:
- Shows all metadata: ID (UUID), Key, Value (full), Type, Label, Description, Created At, Updated At
- Returns error if parameter doesn't exist
**Examples**:
```bash
php bin/console platform-parameter:get max_uploads
php bin/console platform-parameter:get site_name
```
**Use case**: Inspect complete parameter details including timestamps
---
### 3. Update a parameter value
```bash
php bin/console platform-parameter:set <key> <value>
```
**Arguments**:
- `key` (required): The parameter key (must exist)
- `value` (required): The new parameter value
**Behavior**:
- Updates the value of an existing parameter
- Automatically detects parameter type and converts value accordingly
- Cache is automatically cleared
- Events are automatically dispatched
- Returns error if parameter doesn't exist
**Examples**:
```bash
# Update integer parameter
php bin/console platform-parameter:set max_uploads 50
# Update boolean parameter
php bin/console platform-parameter:set maintenance_mode true
# Update string parameter
php bin/console platform-parameter:set site_name "My New Site"
# Update JSON parameter
php bin/console platform-parameter:set config '{"key":"value"}'
```
**Use case**:
- Quickly modify parameter values in scripts or deployments
- Update configuration without accessing database or EasyAdmin
- CI/CD pipeline parameter updates
**Note**: To create new parameters, use Doctrine entities directly (see "Writing/Updating Parameters" section).
---
### 4. Delete a parameter
```bash
php bin/console platform-parameter:delete <key> [--force]
```
**Arguments**:
- `key` (required): The parameter key to delete
**Options**:
- `--force` or `-f`: Skip confirmation prompt
**Behavior**:
- Displays parameter details (key, value, type, label) before deletion
- Asks for confirmation in interactive mode (unless `--force` is used)
- Removes the parameter from database
- Clears the cache for the deleted key
- Returns error if parameter doesn't exist
**Examples**:
```bash
# Delete with confirmation prompt
php bin/console platform-parameter:delete old_feature_flag
# Delete without confirmation (useful in scripts)
php bin/console platform-parameter:delete old_feature_flag --force
php bin/console platform-parameter:delete old_feature_flag -f
```
**Use case**:
- Clean up deprecated parameters
- Remove test parameters after debugging
- Automated cleanup scripts
---
### 5. Clear parameter cache
```bash
php bin/console platform-parameter:cache:clear [key]
```
**Arguments**:
- `key` (optional): Specific parameter key to clear (leave empty to clear all)
**Behavior**:
- If `key` is provided: clears cache for that specific parameter
- If `key` is omitted: clears cache for all parameters
- Uses tag-based invalidation if `TagAwareCache` is available (O(1))
- Otherwise fetches all parameters and deletes individually (O(n))
**Examples**:
```bash
# Clear cache for specific parameter
php bin/console platform-parameter:cache:clear site_name
# Clear cache for all parameters
php bin/console platform-parameter:cache:clear
```
**Use case**:
- Manual cache invalidation if automatic cache clearing is disabled
- Force cache refresh after direct database modifications
- Debugging cache-related issues
**Note**: If `clear_cache_on_parameter_update` is enabled (default), cache is automatically cleared when using `set` or `delete` commands, so this command is rarely needed.
---
## Remarks
NEVER commit or push the git repository.
## ๐ References
- **Source code**: `/src`
- **Tests**: `/tests`
- **README**: User documentation
- **Symfony Docs**: https://symfony.com/doc/current/bundles.html
- **PSR-6 Cache**: https://www.php-fig.org/psr/psr-6/
Platform Parameter Bundle is a Symfony bundle for managing global platform parameters stored in the database with type-safe access and caching system.
A simple and robust system: Doctrine Entity + Service Provider + PSR-6 Cache
The architecture follows a layered approach:
Application Layer (Controllers, Services, Commands) interacts with the PlatformParameterProvider service through dependency injection. This provider implements the PlatformParameterProviderInterface and acts as the single entry point for parameter access.
The provider coordinates three underlying components:
All parameters are stored in the platform_parameter database table and accessed through the ORM.
id, key, value, type, label, description, timestampsupdatedAt// src/Entity/AbstractPlatformParameter.php
#[ORM\MappedSuperclass]
abstract class AbstractPlatformParameter
{
private Uuid $id;
private string $key; // Unique identifier
private string $value; // Stored as TEXT
private ParameterType $type;
private string $label;
private ?string $description;
private \DateTimeImmutable $createdAt;
private \DateTimeImmutable $updatedAt;
}
Design Choice: MappedSuperclass allows projects to extend the entity to add custom fields (category, icon, sortOrder, etc.)
AbstractPlatformParameterplatform_parameter#[ORM\Entity]
#[ORM\Table(name: 'platform_parameter')]
class PlatformParameter extends AbstractPlatformParameter
{
// Ready to use, no custom fields
}
7 supported types with built-in validation:
enum ParameterType: string
{
case STRING = 'string'; // Simple text
case INTEGER = 'integer'; // Integer number
case BOOLEAN = 'boolean'; // true/false/1/0/yes/no
case JSON = 'json'; // JSON object/array
case LIST = 'list'; // Multi-line or delimiter-separated
case FLOAT = 'float'; // Decimal number
case DATETIME = 'datetime'; // Date/time with multiple formats
}
Service interface exposing the public API:
interface PlatformParameterProviderInterface
{
public function getString(string $key, ?string $default = null): string;
public function getInt(string $key, ?int $default = null): int;
public function getBool(string $key, ?bool $default = null): bool;
public function getJson(string $key, ?array $default = null): array;
public function getList(string $key, ?array $default = null, string $separator = "\n"): array;
public function getFloat(string $key, ?float $default = null): float;
public function getDateTime(string $key, ?\DateTimeImmutable $default = null, ?string $format = null): \DateTimeImmutable;
public function has(string $key): bool;
public function clearCache(?string $key = null): void;
}
Design Pattern: Interface injection for testability and decoupling
Provider implementation with business logic:
Responsibilities:
Execution Flow:
When a parameter is requested, the provider follows this sequence:
{prefix}.{key}ParameterNotFoundExceptionWriter service for updating parameter values from code:
Responsibilities:
Available Methods:
interface PlatformParameterWriterInterface
{
public function setString(string $key, string $value): void;
public function setInt(string $key, int $value): void;
public function setBool(string $key, bool $value): void;
public function setJson(string $key, array $value): void;
public function setList(string $key, array $value, string $separator = "\n"): void;
public function setFloat(string $key, float $value): void;
public function setDateTime(string $key, \DateTimeImmutable $value, ?string $format = null): void;
public function delete(string $key): void;
}
Behavior:
set*() methods throw ParameterNotFoundException if the parameter doesn't existset*() methods throw ParameterTypeMismatchException if the parameter type doesn't match the method typedelete() method throws ParameterNotFoundException if the parameter doesn't existPlatformParameterUpdatedEvent, PlatformParameterDeletedEvent) are dispatched automaticallyDesign Principle: CQRS-style separation: Provider = READ, Writer = WRITE
# config/packages/platform_parameter.yaml
platform_parameter:
entity_class: 'Ecourty\PlatformParameterBundle\Entity\PlatformParameter'
cache_ttl: 3600
cache_key_prefix: 'platform_parameter'
clear_cache_on_parameter_update: true # Auto-clear cache via Doctrine listener
cache_adapter: null # Optional custom cache service
When reading a parameter, for example $provider->getInt('max_uploads', 10), the system follows these steps:
'platform_parameter.max_uploads')AbstractPlatformParameter object is returned directlyfindOneBy(['key' => 'max_uploads']) queryParameterNotFoundException is thrown if no default was providedThe bundle provides a PlatformParameterWriter service for type-safe parameter updates from code:
// In a service or controller
public function __construct(
private PlatformParameterWriterInterface $writer,
) {
}
public function updateParameter(): void
{
// Update existing parameters with type-safe methods
$this->writer->setInt('max_uploads', 100);
$this->writer->setBool('maintenance_mode', true);
$this->writer->setString('site_name', 'My Site');
$this->writer->setJson('config', ['key' => 'value']);
$this->writer->setList('emails', ['[email protected]', '[email protected]']);
$this->writer->setFloat('rate', 3.14);
$this->writer->setDateTime('last_sync', new \DateTimeImmutable());
// Delete a parameter
$this->writer->delete('old_parameter');
}
Important: The Writer can only update existing parameters. It throws ParameterNotFoundException if the parameter doesn't exist, and ParameterTypeMismatchException if you try to set a value with the wrong type (e.g., calling setInt() on a STRING parameter).
Why no auto-creation? Because if you extend AbstractPlatformParameter with custom fields (category, icon, etc.), the Writer doesn't know how to populate those fields. Parameter creation must be done explicitly via Doctrine entities.
To create a parameter, you must work directly with Doctrine entities:
// In a service or controller
$parameter = new PlatformParameter();
$parameter->setKey('max_uploads');
$parameter->setValue('20');
$parameter->setType(ParameterType::INTEGER);
$parameter->setLabel('Maximum Uploads');
$parameter->setDescription('Maximum number of files per upload');
$em->persist($parameter);
$em->flush();
By default, cache is automatically cleared via a Doctrine listener when parameters are created, updated, or deleted. You can disable this behavior by setting clear_cache_on_parameter_update: false in configuration if you prefer manual cache management.
The bundle dispatches Symfony events whenever parameters are created, updated, or deleted. This allows host projects to easily react to parameter changes without configuring Doctrine listeners.
Available Events:
Example: React to parameter changes
// src/EventSubscriber/ParameterChangeSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Ecourty\PlatformParameterBundle\Event\PlatformParameterUpdatedEvent;
use Ecourty\PlatformParameterBundle\Event\PlatformParameterCreatedEvent;
use Ecourty\PlatformParameterBundle\Event\PlatformParameterDeletedEvent;
class ParameterChangeSubscriber implements EventSubscriberInterface
{
public function __construct(
private SlackNotifier $slack,
private LoggerInterface $logger,
) {
}
public static function getSubscribedEvents(): array
{
return [
PlatformParameterCreatedEvent::class => 'onParameterCreated',
PlatformParameterUpdatedEvent::class => 'onParameterUpdated',
PlatformParameterDeletedEvent::class => 'onParameterDeleted',
];
}
public function onParameterCreated(PlatformParameterCreatedEvent $event): void
{
$parameter = $event->getParameter();
$this->logger->info('Parameter created', [
'key' => $parameter->getKey(),
'value' => $parameter->getValue(),
]);
}
public function onParameterUpdated(PlatformParameterUpdatedEvent $event): void
{
$parameter = $event->getParameter();
// Send Slack notification for critical parameters
if ($parameter->getKey() === 'maintenance_mode') {
$this->slack->send(sprintf(
'Maintenance mode changed from "%s" to "%s"',
$event->getOldValue(),
$event->getNewValue()
));
}
}
public function onParameterDeleted(PlatformParameterDeletedEvent $event): void
{
$parameter = $event->getParameter();
$this->logger->warning('Parameter deleted', [
'key' => $parameter->getKey(),
'last_value' => $parameter->getValue(),
]);
}
}
Event Details:
Events are always dispatched, regardless of the clear_cache_on_parameter_update configuration setting. They work with:
platform-parameter:set, platform-parameter:delete)$em->persist(), $em->flush(), $em->remove())Cache Management: By default, cache is automatically cleared when parameters change. You can disable this behavior by setting clear_cache_on_parameter_update: false in configuration, but events will still be dispatched, allowing you to implement custom cache invalidation logic via event subscribers.
The PlatformParameterProvider acts as a uniform facade for retrieving parameters.
Each method returns a strict PHP 8.3 type, preventing type errors.
$value = $provider->getInt('unknown_key', 100); // Returns 100 if not found
$value = $provider->getInt('unknown_key'); // Throws ParameterNotFoundException
The cache is checked first, the DB is only consulted in case of MISS.
If TagAwareCacheInterface is available, the bundle uses tags for efficient invalidation.
// Clear all parameters
$provider->clearCache();
// With TagAwareCache: 1 operation
โ invalidateTags(['platform_parameter'])
// Without TagAwareCache: N operations (fetch all + delete each)
โ findAll() โ deleteItem() for each key
AbstractPlatformParameter allows inheritance for extension without modifying the core.
if ($provider->getBool('feature_new_dashboard', false)) {
return $this->render('dashboard_v2.html.twig');
}
$maxFileSize = $provider->getInt('max_upload_size_mb', 10) * 1024 * 1024;
if ($file->getSize() > $maxFileSize) {
throw new UploadException('File too large');
}
$apiConfig = $provider->getJson('stripe_config', [
'public_key' => '',
'secret_key' => '',
'webhook_url' => '',
]);
$stripe = new StripeClient($apiConfig['secret_key']);
$allowedEmails = $provider->getList('beta_testers_emails', []);
if (!in_array($user->getEmail(), $allowedEmails)) {
throw new AccessDeniedException();
}
// In an EventSubscriber
if ($provider->getBool('maintenance_mode', false)) {
throw new ServiceUnavailableHttpException(3600, 'Under maintenance');
}
To add custom metadata: category, sort order, icon, tags, etc.
Step 1: Create a custom entity
namespace App\Entity;
#[ORM\Entity]
#[ORM\Table(name: 'platform_parameter')]
class CustomPlatformParameter extends AbstractPlatformParameter
{
#[ORM\Column(type: 'string', length: 100, nullable: true)]
private ?string $category = null;
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $sortOrder = null;
// Getters/setters...
}
Step 2: Configure the bundle
platform_parameter:
entity_class: 'App\Entity\CustomPlatformParameter'
Step 3: Migration
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
โ The provider continues to work as is - autowiring automatically uses your custom entity.
The bundle provides a ready-to-use CRUD controller if EasyAdmin is installed.
// src/Controller/Admin/PlatformParameterCrudController.php
namespace App\Controller\Admin;
use Ecourty\PlatformParameterBundle\Controller\PlatformParameterCrudController as BaseCrudController;
class PlatformParameterCrudController extends BaseCrudController
{
// Ready to use!
}
{prefix}.{parameter_key} (e.g., platform_parameter.max_uploads)# config/packages/cache.yaml
framework:
cache:
pools:
platform_parameter.cache:
adapter: cache.adapter.redis.tag_aware
default_lifetime: 3600
Benefits:
clearCache()If you have many critical parameters:
// Command or EventListener
foreach ($criticalKeys as $key) {
try {
$provider->getString($key);
} catch (ParameterNotFoundException) {
// Log missing parameter
}
}
Thrown when a parameter doesn't exist and no default is provided.
try {
$value = $provider->getString('missing_key');
} catch (ParameterNotFoundException $e) {
// Log or handle
$value = 'fallback';
}
Thrown during type conversion if the value is invalid.
// DB value: "not_a_number"
$provider->getInt('bad_value'); // throws InvalidArgumentException
// โ Risky
$limit = $provider->getInt('rate_limit');
// โ
Safe
$limit = $provider->getInt('rate_limit', 100);
if ($this->environment === 'dev' && !$provider->has('critical_param')) {
throw new \RuntimeException('Missing critical parameter');
}
$value = $provider->getString('critical_param', 'default');
use Ecourty\PlatformParameterBundle\Contract\PlatformParameterProviderInterface;
class MyServiceTest extends TestCase
{
public function testWithMockProvider(): void
{
$provider = $this->createMock(PlatformParameterProviderInterface::class);
$provider->method('getInt')
->willReturnMap([
['max_uploads', null, 20],
['timeout', null, 30],
]);
$service = new MyService($provider);
// Test service behavior
}
}
// Create test parameters
$parameter = new PlatformParameter();
$parameter->setKey('test_key');
$parameter->setValue('42');
$parameter->setType(ParameterType::INTEGER);
$this->entityManager->persist($parameter);
$this->entityManager->flush();
// Test provider
$result = $this->parameterProvider->getInt('test_key');
$this->assertSame(42, $result);
$provider->clearCache('key') or via consoleSELECT * FROM platform_parameter WHERE key = '...'$provider->clearCache('key') or php bin/console platform-parameter:cache:clear keytype column and called methodThe bundle provides 5 Symfony commands for managing parameters via the console.
php bin/console platform-parameter:list
Features:
Use case: Quick overview of all platform parameters
php bin/console platform-parameter:get <key>
Arguments:
key (required): The parameter key to displayFeatures:
Examples:
php bin/console platform-parameter:get max_uploads
php bin/console platform-parameter:get site_name
Use case: Inspect complete parameter details including timestamps
php bin/console platform-parameter:set <key> <value>
Arguments:
key (required): The parameter key (must exist)value (required): The new parameter valueBehavior:
Examples:
# Update integer parameter
php bin/console platform-parameter:set max_uploads 50
# Update boolean parameter
php bin/console platform-parameter:set maintenance_mode true
# Update string parameter
php bin/console platform-parameter:set site_name "My New Site"
# Update JSON parameter
php bin/console platform-parameter:set config '{"key":"value"}'
Use case:
Note: To create new parameters, use Doctrine entities directly (see "Writing/Updating Parameters" section).
php bin/console platform-parameter:delete <key> [--force]
Arguments:
key (required): The parameter key to deleteOptions:
--force or -f: Skip confirmation promptBehavior:
--force is used)Examples:
# Delete with confirmation prompt
php bin/console platform-parameter:delete old_feature_flag
# Delete without confirmation (useful in scripts)
php bin/console platform-parameter:delete old_feature_flag --force
php bin/console platform-parameter:delete old_feature_flag -f
Use case:
php bin/console platform-parameter:cache:clear [key]
Arguments:
key (optional): Specific parameter key to clear (leave empty to clear all)Behavior:
key is provided: clears cache for that specific parameterkey is omitted: clears cache for all parametersTagAwareCache is available (O(1))Examples:
# Clear cache for specific parameter
php bin/console platform-parameter:cache:clear site_name
# Clear cache for all parameters
php bin/console platform-parameter:cache:clear
Use case:
Note: If clear_cache_on_parameter_update is enabled (default), cache is automatically cleared when using set or delete commands, so this command is rarely needed.
NEVER commit or push the git repository.
/src/testsHow can I help you explore Laravel packages today?