sidus/eav-model-bundle
Symfony/Doctrine bundle to build dynamic EAV models from versioned YAML config. Generate forms automatically, validate/translate/serialize easily, and contextualize attribute data across unlimited axes (language, region, channel, version, etc.). Extensible and customizable.
Installation:
composer require sidus/eav-model-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Sidus\EAVModelBundle\SidusEAVModelBundle::class => ['all' => true],
];
Define a Model:
Create a YAML config file (e.g., config/eav_models/my_model.yml):
my_model:
class: App\Entity\MyModel
attributes:
- name: title
type: string
required: true
- name: description
type: text
required: false
Generate Entities: Run the bundle’s command to scaffold entities:
php bin/console sidus:eav:generate
First Use Case: Fetch and manipulate data via the repository:
$repository = $this->getDoctrine()->getRepository(MyModel::class);
$model = $repository->findOneBy(['id' => 1]);
$model->setAttribute('title', 'New Title');
$repository->save($model);
config/eav_models/ for YAML definitions.App\Entity\* paths are correct in YAML.Documentation/09-context.md for multi-axis contextualization (e.g., language, user role).Sidus\EAVModelBundle\Form\Type\EAVModelType for dynamic forms (see Documentation/05.1-form.md).Dynamic Attribute Management:
# config/eav_models/my_model.yml
attributes:
- name: new_field
type: boolean
default: false
php bin/console sidus:eav:generate
Contextual Data Handling:
language, user_group):
contexts:
- name: language
type: string
default: en
$model->setContext('language', 'fr');
$value = $model->getAttribute('title', [], ['language' => 'fr']);
Form Integration:
$form = $this->createForm(EAVModelType::class, $model, [
'model_config' => 'my_model',
'contexts' => ['language' => 'en'],
]);
Querying:
$models = $repository->findBy([
'attributes.title' => 'Search Term',
'contexts.language' => 'es',
]);
Symfony Events:
Listen to sidus_eav_model.pre_save or sidus_eav_model.post_load for custom logic:
$eventDispatcher->addListener(SidusEAVModelEvents::PRE_SAVE, function (PreSaveEvent $event) {
$model = $event->getModel();
// Custom validation or transformation
});
Validation: Leverage Symfony’s validators via YAML:
attributes:
- name: email
type: string
validation: { constraints: { Email: ~ } }
APIs:
Use serializers with @Groups for API responses:
#[Groups(['api'])]
public function getAttribute(string $name, array $context = [], array $contexts = []): ?string
Testing: Mock contexts in tests:
$model = $repository->find(1);
$model->setContext('test_context', 'value');
$this->assertEquals('value', $model->getContext('test_context'));
YAML Parsing Errors:
type: str instead of type: string) cause silent failures.Context Overrides:
$model->getAttribute('title', [], ['language' => 'fr', 'user_group' => 'admin']);
Attribute Type Mismatches:
string in a boolean field (or vice versa) corrupts data.attributes:
- name: is_active
type: boolean
default: false
validation: { constraints: { Type: { type: "bool" } } }
Regeneration Pitfalls:
sidus:eav:generate after adding a required attribute to an existing model may break data.required: false first, migrate data, then set required: true and regenerate.Doctrine Events: Enable SQL logging to debug queries:
# config/packages/dev/doctrine.yaml
doctrine:
dbal:
logging: true
profiling: true
Context Dumping: Log contexts for debugging:
\Symfony\Component\Debug\Debug::dump($model->getContexts());
Form Errors:
php bin/console cache:clear
$form->getErrors(true); // Deep errors
Custom Attribute Types:
Extend Sidus\EAVModelBundle\Model\Attribute\AttributeInterface for custom types (e.g., Json, DateRange):
class JsonAttribute implements AttributeInterface
{
public function getValue($model, $context = []): ?string
{
return json_encode($model->getRawAttribute($this->getName()));
}
}
Register in YAML:
attributes:
- name: metadata
type: json
Custom Context Providers:
Implement Sidus\EAVModelBundle\Context\ContextProviderInterface to inject dynamic contexts (e.g., from a session):
class UserContextProvider implements ContextProviderInterface
{
public function getContexts(): array
{
return ['user_id' => $this->getUserId()];
}
}
Register as a service:
services:
Sidus\EAVModelBundle\Context\ContextProviderInterface:
tags: ['sidus_eav.context_provider']
Event Subscribers: Extend model behavior via events (e.g., audit logs):
$eventDispatcher->addListener(SidusEAVModelEvents::POST_SAVE, function (PostSaveEvent $event) {
$logger->info('Model saved', ['model_id' => $event->getModel()->getId()]);
});
Default Contexts:
contexts:
- name: language
default: en
Attribute Inheritance:
extends field (not natively supported; requires custom logic).Case Sensitivity:
snake_case) and avoid mixed case.Context Indexing:
// In a custom repository method
$qb->andWhere('m.contexts LIKE :context')
->setParameter('context', '%"language":"fr"%');
Batch Updates:
BATCH_SIZE for bulk operations:
# config/packages/doctrine.yaml
doctrine:
orm:
dql:
string_trim: true
batch_size: 50
How can I help you explore Laravel packages today?