Skip to content

Commit 3a4ccdb

Browse files
committed
add docs
1 parent 4e9cf33 commit 3a4ccdb

7 files changed

Lines changed: 213 additions & 0 deletions

File tree

user_guide_src/source/changelogs/v4.8.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ Validation
332332
Others
333333
======
334334

335+
- **Config:** Added optional ``CodeIgniter\Config\Merge`` directives for :ref:`Registrars <registrars>` to control replacing, deep merging, and ordered list additions. Existing registrar merge behavior is unchanged. See :ref:`registrar-merge-directives`.
335336
- **Float and Double Casting:** Added support for precision and rounding mode when casting to float or double in entities.
336337
- Added ``CodeIgniter\Input\InputData``, ``ValidatedInput``, and ``InputDataFactory`` for reusable typed input data objects.
337338
- Float and Double casting now throws ``CastException::forInvalidFloatRoundingMode()`` if an rounding mode other than up, down, even or odd is provided.

user_guide_src/source/general/configuration.rst

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,99 @@ Registrar methods must always return an array, with keys corresponding to the pr
342342
of the target config file. Existing values are merged, and Registrar properties have
343343
overwrite priority.
344344

345+
By default this merge is **shallow** (top-level only). Arrays are combined with
346+
``array_merge()``, so a nested array under a top-level key *replaces* the existing
347+
nested array rather than merging into it. In the following example the ``key2``
348+
subtree from the config is replaced entirely, dropping ``val2`` and ``val3``:
349+
350+
.. literalinclude:: configuration/012.php
351+
352+
.. _registrar-merge-directives:
353+
354+
Controlling how values are merged
355+
---------------------------------
356+
357+
.. versionadded:: 4.8.0
358+
359+
When a registrar needs finer control than the shallow default, it can return a
360+
``CodeIgniter\Config\Merge`` directive as the value of a property. Merge
361+
directives are explicit instructions to either keep merging into a value, replace
362+
it, or add items to a list.
363+
364+
The two directives that control whole values are:
365+
366+
- ``Merge::replace($value)`` - discard the existing value and use ``$value``.
367+
Accepts **any type** (scalar, ``null``, or array - e.g. ``['a', 'b']`` becomes
368+
``['c']``, or ``Merge::replace('redis')``).
369+
- ``Merge::byKey($value)`` - deep-merge by key: **string keys recurse, integer
370+
keys append, and scalar leaves are replaced**. The name is deliberately not
371+
``recursive`` to avoid confusion with PHP's ``array_merge_recursive()``, which
372+
collects scalar values into arrays instead of replacing them.
373+
374+
Use ``Merge::byKey()`` when you want to navigate into nested configuration and
375+
preserve sibling keys:
376+
377+
.. literalinclude:: configuration/013.php
378+
379+
.. important:: Inside ``Merge::byKey()``, plain arrays are still merged by key.
380+
Use ``Merge::replace()`` when you want to stop merging at that key and
381+
overwrite the value. For example, ``'after' => []`` leaves an existing
382+
``after`` list unchanged, while ``'after' => Merge::replace([])`` clears it.
383+
384+
The following registrar adds a filter to ``globals['before']`` while hard-resetting
385+
``globals['after']``, leaving any other ``globals`` keys untouched:
386+
387+
.. literalinclude:: configuration/014.php
388+
389+
The *list* strategies add items to a list and control where they land. They are
390+
useful where order matters, such as the filter lists in ``Config\Filters``:
391+
392+
- ``Merge::append($value)`` - add items to the **end** of the list
393+
(e.g. ``['a', 'b']`` becomes ``['a', 'b', 'c']``).
394+
- ``Merge::prepend($value)`` - add items to the **front** of the list
395+
(e.g. ``['a', 'b']`` becomes ``['c', 'a', 'b']``).
396+
- ``Merge::before($anchor, $value)`` - insert items immediately **before** the
397+
first element equal to ``$anchor``.
398+
- ``Merge::after($anchor, $value)`` - insert items immediately **after** the
399+
first element equal to ``$anchor``.
400+
401+
All four de-duplicate, so the directives never *introduce* a duplicate value: a
402+
value already in the list is not added again, and duplicate payload values are
403+
collapsed (e.g. ``Merge::append(['x', 'x'])`` adds ``x`` once). Duplicates that
404+
already exist in the current list are left as-is. They differ only in how they
405+
treat a value that is *already present*:
406+
407+
- ``append()`` / ``prepend()`` leave an already-present value **where it is**
408+
(they only add values that are missing).
409+
- ``before()`` / ``after()`` **move** an already-present value to the anchor
410+
position - but only when the anchor is in the list. If the anchor is missing
411+
they fall back to ``append()`` / ``prepend()`` respectively, and do **not**
412+
relocate a value that is already present.
413+
414+
The anchor is matched strictly against the **direct elements** of the list; the
415+
list strategies act on a single list level and never recurse. To reach a list
416+
that is nested under other keys (such as ``globals['before']``), navigate to it
417+
with ``Merge::byKey()`` and place the list directive at that key:
418+
419+
.. literalinclude:: configuration/015.php
420+
421+
.. important:: Merge directives are interpreted only when used as the **value of
422+
a config property** returned by a registrar, and recursively **inside**
423+
``Merge::byKey()``. The payloads of the ``replace()``, ``append()``,
424+
``prepend()``, ``before()``, and ``after()`` strategies are taken literally and
425+
are **not** scanned for nested directives - for nested control, wrap the
426+
property in ``Merge::byKey()`` and place the directives at its keys.
427+
428+
.. note:: Merge directives sharpen a *single* registrar's intent; they do not add
429+
an explicit priority mechanism between registrars. Registrars are still applied
430+
in discovery order, and an item's final position follows from that order *plus*
431+
the strategy: with ``append()`` an earlier registrar's items sit ahead of a
432+
later one's, while with ``prepend()``, ``before()``, and ``after()`` a later
433+
registrar's items land **nearer the front or the anchor** than an earlier one's.
434+
For example, ``after('csrf', ['a'])`` followed by ``after('csrf', ['b'])`` from a
435+
second registrar yields ``['csrf', 'b', 'a']``. As always, **.env** values take
436+
priority over registrars.
437+
345438
Explicit Registrars
346439
===================
347440

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
// app/Config/Example.php
4+
5+
namespace Config;
6+
7+
use CodeIgniter\Config\BaseConfig;
8+
9+
class Example extends BaseConfig
10+
{
11+
public array $arrayNested = [
12+
'key1' => 'val1',
13+
'key2' => ['val2' => 'subVal2', 'val3' => 'subVal3'],
14+
];
15+
}
16+
17+
// Modules/MyModule/Config/Registrar.php - plain array (shallow merge)
18+
19+
namespace MyModule\Config;
20+
21+
class Registrar
22+
{
23+
public static function Example(): array
24+
{
25+
return ['arrayNested' => ['key2' => ['val4' => 'subVal4']]];
26+
}
27+
}
28+
29+
// Result - the nested array under "key2" is replaced wholesale, so
30+
// "val2" and "val3" are silently dropped:
31+
//
32+
// 'arrayNested' => [
33+
// 'key1' => 'val1',
34+
// 'key2' => ['val4' => 'subVal4'],
35+
// ]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
// Modules/MyModule/Config/Registrar.php - opt into a deep merge with Merge::byKey()
4+
5+
namespace MyModule\Config;
6+
7+
use CodeIgniter\Config\Merge;
8+
9+
class Registrar
10+
{
11+
public static function Example(): array
12+
{
13+
return [
14+
'arrayNested' => Merge::byKey([
15+
'key2' => ['val4' => 'subVal4'],
16+
]),
17+
];
18+
}
19+
}
20+
21+
// Result - the sibling keys are preserved:
22+
//
23+
// 'arrayNested' => [
24+
// 'key1' => 'val1',
25+
// 'key2' => ['val2' => 'subVal2', 'val3' => 'subVal3', 'val4' => 'subVal4'],
26+
// ]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
// Modules/MyModule/Config/Registrar.php - nested directives inside Merge::byKey()
4+
5+
namespace MyModule\Config;
6+
7+
use CodeIgniter\Config\Merge;
8+
9+
class Registrar
10+
{
11+
public static function Filters(): array
12+
{
13+
return [
14+
'globals' => Merge::byKey([
15+
'before' => Merge::append(['blogFilter']), // add to the existing list
16+
'after' => Merge::replace([]), // hard reset, plain [] would keep merging
17+
]),
18+
];
19+
}
20+
21+
// Scalar replace also works at the property root:
22+
public static function Cache(): array
23+
{
24+
return ['handler' => Merge::replace('redis')];
25+
}
26+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
// Modules/MyModule/Config/Registrar.php - order filters relative to existing ones
4+
5+
namespace MyModule\Config;
6+
7+
use CodeIgniter\Config\Merge;
8+
9+
class Registrar
10+
{
11+
public static function Filters(): array
12+
{
13+
return [
14+
'globals' => Merge::byKey([
15+
// Run "auth" immediately after "csrf" in the before-list.
16+
'before' => Merge::after('csrf', ['auth']),
17+
// Run "honeypot" first in the after-list.
18+
'after' => Merge::prepend(['honeypot']),
19+
]),
20+
];
21+
}
22+
}
23+
24+
// Given a base of:
25+
// 'before' => ['csrf', 'invalidchars'],
26+
// 'after' => ['toolbar'],
27+
// the result is:
28+
// 'before' => ['csrf', 'auth', 'invalidchars'],
29+
// 'after' => ['honeypot', 'toolbar'],

user_guide_src/source/general/modules.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ Config files are automatically discovered whenever using the :php:func:`config()
206206

207207
.. note:: We don't recommend you use the same short classname in modules.
208208
Modules that need to override or add to known configurations in **app/Config/** should use :ref:`Implicit Registrars <registrars>`.
209+
To contribute to *nested* configuration (filters, permission matrices, and so on)
210+
without clobbering existing values, use the
211+
:ref:`merge directives <registrar-merge-directives>`.
209212

210213
.. note:: Prior to v4.4.0, ``config()`` finds the file in **app/Config/** when there
211214
is a class with the same shortname,

0 commit comments

Comments
 (0)