-
Notifications
You must be signed in to change notification settings - Fork 0
Extending
ParameterBag is intentionally not final. v2 documents two
protected hooks as stable override points; everything else
(private properties, private helpers, the static sentinel) is
implementation detail and may change without a major-version bump.
| Hook | Purpose |
|---|---|
getKey |
Customise how caller-supplied keys are normalised (case fold + separator trim by default). |
setOptions |
Recognise new option keys, validate them, then delegate to the parent for built-in handling. |
Both are called from the constructor and from every public
mutator (get, set, has, remove), so overrides take effect
on the entire surface without further plumbing.
The default getKey() does two things in order:
- Folds the key to lower-case if
caseInsensitiveis on. - Trims leading / trailing separators if
isMultiis on.
protected function getKey(string $key): string
{
if ($this->caseInsensitive) {
$key = strtolower($key);
}
if ($this->isMulti) {
$key = trim($key, $this->separator);
}
return $key;
}A subclass that accepts camelCase and kebab-case callers but
stores everything as snake_case:
use InitPHP\ParameterBag\ParameterBag;
final class SnakeCaseBag extends ParameterBag
{
protected function getKey(string $key): string
{
$key = parent::getKey($key);
// camelCase → camel_case, dashes → underscores
$key = preg_replace('/([a-z])([A-Z])/', '$1_$2', $key);
$key = str_replace('-', '_', (string) $key);
return strtolower($key);
}
}
$bag = new SnakeCaseBag();
$bag->set('userName', 'alice');
$bag->set('first-name', 'Alice');
$bag->get('user_name'); // 'alice'
$bag->get('first_name'); // 'Alice'
$bag->keys(); // ['user_name', 'first_name']Note: getKey() only runs against caller-supplied keys; it does
not rewrite the keys of a constructor payload. If you need the
payload itself rewritten, override replace() or normalise the
data before construction.
final class AliasedBag extends ParameterBag
{
/** @var array<string, string> */
private const ALIASES = [
'user' => 'username',
'pwd' => 'password',
'database' => 'db',
];
protected function getKey(string $key): string
{
$key = parent::getKey($key);
return self::ALIASES[$key] ?? $key;
}
}setOptions() is the only place options are read, so adding a
new one is a single override. Validate your additions first, then
hand off to the parent for the built-in ones:
use InitPHP\ParameterBag\ParameterBag;
use InitPHP\ParameterBag\Exception\ParameterBagInvalidArgumentException;
final class ConfigurableBag extends ParameterBag
{
/** @var int Maximum number of top-level entries; 0 disables the cap. */
private int $maxEntries = 0;
protected function setOptions(array $options): void
{
if (isset($options['maxEntries'])) {
if (!is_int($options['maxEntries']) || $options['maxEntries'] < 0) {
throw new ParameterBagInvalidArgumentException(
'maxEntries must be a non-negative integer.'
);
}
$this->maxEntries = $options['maxEntries'];
unset($options['maxEntries']); // strip before parent validates
}
parent::setOptions($options); // validates the rest
}
public function set(string $key, $value): \InitPHP\ParameterBag\ParameterBagInterface
{
if (
$this->maxEntries > 0
&& !$this->has($key)
&& $this->count() >= $this->maxEntries
) {
throw new ParameterBagInvalidArgumentException(sprintf(
'Bag is full (maxEntries=%d).',
$this->maxEntries,
));
}
return parent::set($key, $value);
}
}
$bag = new ConfigurableBag([], ['maxEntries' => 2]);
$bag->set('a', 1)->set('b', 2);
$bag->set('c', 3);
// ParameterBagInvalidArgumentException: Bag is full (maxEntries=2).Key points of this pattern:
- Strip your option out of
$optionsbefore delegating, so the parent's strict validation does not reject it. - Call
parent::setOptions()last so the built-in options still receive their normal validation. - If you reject the value yourself, throw
ParameterBagInvalidArgumentException(or a subclass) — the rest of the library only uses this type.
The following are not stable override points; they are private helpers and may be renamed, inlined, or removed in any minor release:
- The private property layout (
$stack,$isMulti,$separator,$caseInsensitive). - The static
$notFoundsentinel. -
normalizeKeys(),multiSubParameterGet(),multiSubParameterSet(),multiSubParameterRemove(),arrayOrEmpty().
If you need to alter behaviour that lives there, prefer wrapping
the bag in a decorator that implements ParameterBagInterface
rather than reaching into the implementation.
If your customisation does not need to participate in the bag's internal state, write a decorator instead:
use InitPHP\ParameterBag\ParameterBagInterface;
final class ReadOnlyBag implements ParameterBagInterface
{
public function __construct(private ParameterBagInterface $inner) {}
public function get(string $key, $default = null) { return $this->inner->get($key, $default); }
public function has(string $key): bool { return $this->inner->has($key); }
public function all(): array { return $this->inner->all(); }
public function isEmpty(): bool { return $this->inner->isEmpty(); }
public function keys(): array { return $this->inner->keys(); }
public function values(): array { return $this->inner->values(); }
public function count(): int { return $this->inner->count(); }
public function getIterator(): \Traversable { return $this->inner->getIterator(); }
public function offsetExists($o): bool { return $this->inner->offsetExists($o); }
public function offsetGet($o) { return $this->inner->offsetGet($o); }
public function set(string $key, $value): self { throw new \LogicException('read-only'); }
public function remove(string ...$keys): self { throw new \LogicException('read-only'); }
public function merge(...$merge): self { throw new \LogicException('read-only'); }
public function replace(array $data): self { throw new \LogicException('read-only'); }
public function clear(): void { throw new \LogicException('read-only'); }
public function close(): void { throw new \LogicException('read-only'); }
public function offsetSet($o, $v): void { throw new \LogicException('read-only'); }
public function offsetUnset($o): void { throw new \LogicException('read-only'); }
}A decorator is the right choice when you want to compose behaviours (read-only + logging, freeze-after-boot, etc.) or when the override would otherwise need to touch implementation internals.
initphp/parameterbag · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core Usage
Reference
Practical Guides
Migration & Help