Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
symfony:
- '5.4'
- '6.4'
- '7.2'
- '7.4'
minimumStability:
- 'stable'
composerOptions:
Expand Down
59 changes: 59 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# PROJECT KNOWLEDGE BASE

## OVERVIEW

Reusable Symfony bundle for declaring named dictionaries in application configuration and consuming them through DI, forms, validation, Twig, Faker, and the web profiler. PHP 8.1+; Symfony 5.4, 6.4, and 7.x; Twig 2.15/3.x.

## STRUCTURE

```text
DictionaryBundle/
├── src/Knp/DictionaryBundle/ # PSR-4 runtime package; legacy nested bundle layout
├── spec/Knp/DictionaryBundle/ # PHPSpec tree mirroring runtime namespaces
├── spec/PHPSpec/ # Local extension and shouldBeOneOf matcher
├── bin/lint-twig # Standalone Twig syntax check
└── .github/workflows/test.yaml # Compatibility matrix and quality gates
```

## WHERE TO LOOK

| Task | Location | Notes |
|------|----------|-------|
| Bundle bootstrap | `src/Knp/DictionaryBundle/KnpDictionaryBundle.php` | Registers compiler passes in significant order |
| User configuration | `src/Knp/DictionaryBundle/DependencyInjection/Configuration.php` | Normalizes `knp_dictionary.dictionaries` |
| Service composition | `src/Knp/DictionaryBundle/Resources/config/services.yaml` | Imports explicit service fragments |
| Dictionary behavior | `src/Knp/DictionaryBundle/Dictionary/` | Implementations, collection, wrappers, factories |
| Framework adapters | `Form/`, `Validator/`, `Templating/`, `Faker/`, `DataCollector/` | All consume the shared collection |
| Behavioral specs | `spec/Knp/DictionaryBundle/` | Mirrors source paths; no PHPUnit suite |

## CONVENTIONS

- Runtime namespace maps to `src/Knp/DictionaryBundle`; specs map `spec\Knp\` to `spec/Knp`.
- PHP files use strict types, final concrete classes, and ordered imports/interfaces.
- DI YAML uses explicit FQCN service IDs, arguments, and tags. No broad autowire/autoconfigure resource block.
- Services are private except public API entry points: `Dictionary\Collection` and the `Dictionary\Factory` alias.
- Behavior changes require a matching PHPSpec example; specs keep data inline and use Prophecy collaborators.

## ANTI-PATTERNS (THIS PROJECT)

- Do not eagerly evaluate callable or iterator-backed dictionaries. Their documented contract is lazy until first value access.
- Do not treat the README's `knp_dictionary.value_transformer` tag as implemented. No compiler pass or autoconfiguration consumes it.
- Do not reorder compiler passes or tagged factory definitions without tracing construction order and running the relevant specs.
- Do not copy the README's PHPStan or Rector commands; both sections are stale. Use the commands below.

## COMMANDS

```bash
composer install
vendor/bin/phpspec run -v --config=phpspec.no-coverage.yml
vendor/bin/phpstan analyse --no-progress --memory-limit=-1
bin/lint-twig src/
PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --diff --dry-run -vvv
vendor/bin/rector process --dry-run
```

## NOTES

- CI rewrites `composer.json` to test Symfony minors with lowest and current dependencies; avoid relying on a single local dependency set.
- PHPStan level 8 covers `src` plus `spec/PHPSpec`, not the mirrored behavior specs.
- CI currently excludes PHP 8.4 pending PHPSpec compatibility.
115 changes: 69 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
DictionaryBundle
================
# DictionaryBundle

[![CircleCI](https://circleci.com/gh/KnpLabs/DictionaryBundle.svg?style=svg)](https://circleci.com/gh/KnpLabs/DictionaryBundle)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/KnpLabs/DictionaryBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/KnpLabs/DictionaryBundle/?branch=master)

Expand All @@ -8,15 +8,17 @@ Are you often tired to repeat static choices like gender or civility in your app
## Requirements

- PHP >= 8.1
- Symfony 5.4, 6.4 or 7.*
- Symfony 5.4, 6.4 or 7.\*

## Installation

Run the following command:

```bash
composer require knplabs/dictionary-bundle
```
Register the bundle in ``config/bundles.php``

Register the bundle in `config/bundles.php`

```php
$bundles = array(
Expand All @@ -29,21 +31,24 @@ $bundles = array(

You can ping us if need some reviews/comments/help:

- [@AntoineLelaisant](https://github.com/AntoineLelaisant)
- [@PedroTroller](https://github.com/PedroTroller)
- [@AntoineLelaisant](https://github.com/AntoineLelaisant)
- [@PedroTroller](https://github.com/PedroTroller)

## Basic usage

Define dictionaries in your config.yml file:

```yaml
knp_dictionary:
dictionaries:
my_dictionary: # your dictionary name
- Foo # your dictionary content
- Foo # your dictionary content
- Bar
- Baz
```
You will be able to retreive it by injecting the Collection service and accessing the dictionary by its key

You will be able to retrieve it by injecting the Collection service and accessing the dictionary by its key

```php

private Dictionary $myDictionary;
Expand All @@ -53,7 +58,8 @@ You will be able to retreive it by injecting the Collection service and accessin
$this->myDictionary = $dictionaries['my_dictionary'];
}
```
### Dictionary form type

## Dictionary form type

Now, use them in your forms:

Expand All @@ -69,9 +75,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
;
}
```

The dictionary form type extends the [symfony's choice type](http://symfony.com/fr/doc/current/reference/forms/types/choice.html) and its options.

### Validation constraint
## Validation constraint

You can also use the constraint for validation. The `value` has to be set.

Expand All @@ -80,60 +87,69 @@ use Knp\DictionaryBundle\Validator\Constraints\Dictionary;

class User
{
/**
* @ORM\Column
* @Dictionary(name="my_dictionary")
*/
#[ORM\Column]
#[Dictionary(name: 'my_dictionary')]
private $civility;
}
```

## Advanced usage

You can specify the indexation mode of each dictionary

```yaml
knp_dictionary:
dictionaries:
my_dictionary: # your dictionary name
type: 'key_value' # your dictionary type
content: # your dictionary content
my_dictionary: # your dictionary name
type: "key_value" # your dictionary type
content: # your dictionary content
"foo": "foo_value"
"bar": "bar_value"
"baz": "baz_value"
```
### Available types

## Available types

- `value` (default) : Natural indexation
- `value_as_key`: Keys are defined from their value
- `key_value`: Define your own keys
- `callable`: Build a dictionary from a callable

### Callable dictionary
## Callable dictionary

You can create a callable dictionary:

```yaml
knp_dictionary:
dictionaries:
my_callable_dictionary: # your dictionary name
type: 'callable' # your dictionary type
service: 'app.service.id' # a valid service from your application
method: 'getSomething' # the method name to execute
my_callable_dictionary: # your dictionary name
type: "callable" # your dictionary type
service: "app.service.id" # a valid service from your application
method: "getSomething" # the method name to execute
```

Callable dictionaries are loaded with a lazy strategy. It means that the callable
will not be called if you do not use the dictionary.

### Iterator based dictionary
## Iterator based dictionary

You can create a dictionary from an iterator:

```yaml
knp_dictionary:
dictionaries:
my_iterator_dictionary: # your dictionary name
type: 'iterator' # your dictionary type
service: 'app.service.id' # a valid service from your application
my_iterator_dictionary: # your dictionary name
type: "iterator" # your dictionary type
service: "app.service.id" # a valid service from your application
```
Iterator based dictionaries are loaded with a lazy strategy. It means that the

Iterator based dictionaries are loaded with a lazy strategy. It means that the
iterator will not be fetched if you do not use the dictionary.

### Combined dictionary
## Combined dictionary

You can combine multiple dictionaries into a single one:

```yaml
knp_dictionary:
dictionaries:
Expand All @@ -155,34 +171,38 @@ knp_dictionary:
- payment_mode
- extra_payment_mode
```
Now you have 3 dictionaries, `payment_mode` and `extra_payment_mode` contain

Now you have 3 dictionaries, `payment_mode` and `extra_payment_mode` contain
their own values but `combined_payment_mode` contains all the values of the previous ones.

### Extended dictionary
## Extended dictionary

You can create an extended dictionary:

```yaml
knp_dictionary:
dictionaries:
europe:
type: 'key_value'
type: "key_value"
content:
fr: France
de: Germany

world:
type: 'key_value'
type: "key_value"
extends: europe
content:
us: USA
ca: Canada
```

The dictionary `world` will now contain its own values in addition
to the `europe` values.

**Note**: You must define the initial dictionary **BEFORE** the extended one.

## Transformers

For now, this bundle is only able to resolve your **class constants**:

```yaml
Expand All @@ -191,13 +211,15 @@ my_dictionary:
- Foo
- Bar
```

You want to add other kinds of transformations for your dictionary values ?
Feel free to create your own transformer !

### Add your own transformers
## Add your own transformers

Create your class that implements [TransformerInterface](src/Knp/DictionaryBundle/Dictionary/ValueTransformer/TransformerInterface.php).
Load your transformer and tag it as `knp_dictionary.value_transformer`.

```yaml
services:
App\My\Transformer:
Expand Down Expand Up @@ -225,7 +247,7 @@ But you can also access directly to a value by using the same function (or filte

The KnpDictionaryBundle comes with a [faker provider](https://github.com/FakerPHP/Faker) that can be used to provide a random entry from a dictionary.

### Alice
## Alice

To register the provider in [nelmio/alice](https://github.com/nelmio/alice), you can follow the [official documentation](https://github.com/nelmio/alice/blob/master/doc/customizing-data-generation.md#add-a-custom-faker-provider-class)

Expand All @@ -239,13 +261,13 @@ App\Entity\User:

## Create your own dictionary implementation

### Dictionary
## Dictionary

Your dictionary implementation must implements the interface [Dictionary](src/Knp/DictionaryBundle/Dictionary.php).

It is automaticaly registered with the `autoconfigure: true` DIC feature.
It is automatically registered with the `autoconfigure: true` DIC feature.

Else you can register it by your self:
Else you can register it by your self:

```yaml
services:
Expand All @@ -254,37 +276,38 @@ services:
- knp_dictionary.dictionary
```

### Dictionary Factory
## Dictionary Factory

You must create a dictionary factory that will be responsible to instanciate your dictionary.
You must create a dictionary factory that will be responsible to instantiate your dictionary.

It is automaticaly registered with the `autoconfigure: true` DIC feature.
It is automatically registered with the `autoconfigure: true` DIC feature.

Else you can register it by your self:
Else you can register it by your self:

```yaml
services:
App\Dictionary\Factory\MyCustomFactory:
tags:
- knp_dictionary.factory
```

## Tests

### phpspec
## phpspec

```bash
composer install
vendor/bin/phpspec run
```

### php-cs-fixer
## php-cs-fixer

```bash
composer install
vendor/bin/php-cs-fixer fix
```

### phpstan
## phpstan

First [install phive](https://github.com/phar-io/phive#getting-phive).

Expand All @@ -295,7 +318,7 @@ phive install
tools/phpstan process
```

### rector (*optional*)
## rector (_optional_)

```bash
rector process --set php70 --set php71 --set php72 --set code-quality --set coding-style --set symfony34 --set twig240 --set psr-4 --set solid src/ spec/
Expand Down
Loading
Loading