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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.idea
.psalm-cache
.phpunit.cache
.arraykit-cache
.vscode
.windsurf
*~
Expand All @@ -16,3 +17,4 @@ patch.php
test.php
var
vendor
.codex
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ real-world PHP projects.
- **Pipeline for Collection Ops**
- **LazyCollection for Generator-Based Flows**
- **ArrayShape Validation Helper**
- **Laravel Compatibility Layer (`LaravelCompat\\Arr`, `LaravelCompat\\Collection`)**
- **Compiled Config + Lazy Namespace Cache**
- **Namespaced Helpers + Optional Globals**

## Modules
Expand All @@ -45,8 +45,8 @@ real-world PHP projects.

| Class | Description |
|---------------------|---------------------------------------------------------------------------------------------------------------------|
| **Config** | Dot-access configuration loader with explicit hook-aware variants (`getWithHooks`, `setWithHooks`, `fillWithHooks`). |
| **LazyFileConfig** | First-segment lazy loader (`db.host` loads `db.php` on demand) for lower memory usage on large config trees. |
| **Config** | Dot-access configuration loader with explicit hook-aware variants (`getWithHooks`, `setWithHooks`, `fillWithHooks`) plus compiled cache export/load and read memoization. |
| **LazyFileConfig** | First-segment lazy loader (`db.host` loads `db.php` on demand) with namespace cache files for structural reads and a flat leaf-index cache for exact scalar lookups. |
| **BaseConfigTrait** | Shared config logic. |


Expand Down Expand Up @@ -224,6 +224,11 @@ $config->snapshot('before-runtime');
$config->merge(['app' => ['env' => 'production']]);
$changed = $config->changed('before-runtime');
$config->restore('before-runtime');

// Compiled cache export / load
$config->exportCache(__DIR__ . '/bootstrap/cache/config.php');
$cached = new Config();
$cached->loadCache(__DIR__ . '/bootstrap/cache/config.php');
```

### Hooked Collection
Expand Down Expand Up @@ -266,12 +271,12 @@ $user->hydrate(['name' => 'Alice'], mapping: ['name' => 'full_name']);
$deep = $user->toArrayDeep();
```

### Lazy + Shape + Compat
### Lazy + Shape + Cache

```php
use Infocyph\ArrayKit\Array\ArrayShape;
use Infocyph\ArrayKit\ArrayKit;
use Infocyph\ArrayKit\LaravelCompat\Arr;
use Infocyph\ArrayKit\Config\LazyFileConfig;

$lazy = ArrayKit::lazyCollection(range(1, 10))
->filterLazy(fn ($v) => $v % 2 === 0)
Expand All @@ -283,8 +288,11 @@ $row = ArrayShape::require(
['id' => 'int', 'email' => 'string', 'roles' => 'list<string>'],
);

$data = ['user' => ['name' => 'Alice']];
Arr::set($data, 'user.role', 'admin');
$config = new LazyFileConfig(__DIR__ . '/config', namespaceCacheDirectory: __DIR__ . '/bootstrap/cache/config');
$config->warmNamespaceCache(['db', 'cache']);

// Exact scalar leaf reads can hit bootstrap/cache/config/__flat.php first.
$host = $config->get('db.host');
```

## Behavior Notes
Expand All @@ -296,6 +304,7 @@ Arr::set($data, 'user.role', 'admin');
- `DotNotation` treats existing `null` keys/properties as present (does not fall back to defaults).
- `DotNotation::hasWildcard()`, `paths()`, `matches()`, `rename()`, and `move()` are available for wildcard/path operations.
- For untrusted/deep payloads, use bounded traversal variants: `DotNotation::getSafe()`, `ArrayMulti::depthGuarded()`, `flattenGuarded()`, and `sortRecursiveGuarded()`.
- `LazyFileConfig` namespace cache writes one cache file per namespace plus a shared `__flat.php` file containing only final scalar/null leaf values for exact-key fast paths.

## Security

Expand Down
46 changes: 46 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Important behavior:
- If already loaded, they return ``false`` and do not overwrite existing items.
- ``replace()`` always replaces in-memory config items.
- ``reload()`` replaces from array or readable file path.
- ``exportCache()`` writes a compiled PHP cache file of current items.
- ``loadCache()`` loads a compiled PHP cache file through the normal file loader.
- Facade-based config creation is documented in :doc:`facade`.

Reading Values
Expand Down Expand Up @@ -218,6 +220,30 @@ Use config as a mutable runtime container for app setup:
$config->setWithHooks('app.timezone', ' utc ');
$tz = $config->getWithHooks('app.timezone'); // UTC

Compiled Cache + Read Memoization
---------------------------------

``Config`` also supports two cache layers:

- in-memory read memoization for repeated dot-path lookups
- compiled cache export/load through PHP files

.. code-block:: php

<?php
$config = new Config();
$config->loadArray([
'app' => ['name' => 'ArrayKit'],
'db' => ['host' => 'localhost'],
]);

$config->readCache(); // enabled by default
$config->exportCache(__DIR__.'/bootstrap/cache/config.php');

$cached = new Config();
$cached->loadCache(__DIR__.'/bootstrap/cache/config.php');
$name = $cached->get('app.name');

LazyFileConfig
--------------

Expand Down Expand Up @@ -254,6 +280,21 @@ Important behavior:
- Missing namespace file returns the provided default.
- ``replace()`` and ``reload()`` reset resolved-namespace tracking.
- read-only mode applies to ``set/fill/forget/replace/reload``-style mutators.
- ``namespaceCache()`` configures an optional per-namespace cache directory.
- ``warmNamespaceCache()`` writes cached namespace files and a shared ``__flat.php`` exact-leaf index.
- Exact-key scalar reads check ``__flat.php`` first; structural, wildcard, and namespace reads fall back to namespace cache files.
- Runtime writes only affect in-memory state until cache files are explicitly rebuilt.

.. code-block:: php

<?php
$config = new LazyFileConfig(
__DIR__.'/config',
namespaceCacheDirectory: __DIR__.'/bootstrap/cache/config'
);

$config->warmNamespaceCache(['db', 'cache']);
$host = $config->get('db.host');

Method Summary
--------------
Expand All @@ -266,6 +307,8 @@ Config methods:
- ``set()``, ``fill()``, ``forget()``
- ``prepend()``, ``append()``
- ``replace()``, ``reload()``
- ``exportCache()``, ``loadCache()``
- ``readCache()``, ``readCacheEnabled()``, ``flushReadCache()``
- ``getString()/getInt()/getFloat()/getBool()/getArray()/getList()/getEnum()``
- ``merge()``, ``overlay()``
- ``snapshot()``, ``restore()``, ``changed()``
Expand All @@ -277,7 +320,10 @@ LazyFileConfig methods:
- ``has()``, ``hasAny()``
- ``set()``, ``fill()``, ``forget()``
- ``preload()``, ``isLoaded()``, ``loaded()``, ``loadedNamespaces()``
- ``namespaceCache()``, ``namespaceCacheDirectory()``
- ``warmNamespaceCache()``, ``flushNamespaceCache()``
- ``replace()``, ``reload()``
- ``exportCache()``, ``loadCache()``
- ``all()`` (throws by design)

Hook-aware methods (Config and LazyFileConfig):
Expand Down
4 changes: 3 additions & 1 deletion docs/migration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Recent Additions
- ``Collection`` now implements ``IteratorAggregate`` semantics for safe nested iteration and provides ``copy()`` / ``immutable()`` snapshots.
- ``Config`` / ``LazyFileConfig`` now include ``replace()``, ``reload()``, and ``getOrFail()``.
- ``LazyFileConfig`` includes ``loaded()`` alias for ``isLoaded()``.
- ``Config`` now supports compiled cache export/load plus in-memory read memoization.
- ``LazyFileConfig`` adds namespace-cache warming and full compiled-cache generation.
- Namespaced helpers (``Infocyph\ArrayKit\*``) are now the default autoloaded helper surface; globals are optional via manual include of ``src/functions.php``.
- ``ArrayMulti::flatten($array, 0)`` now returns unchanged top-level values.
- ``ArraySingle::avg()``, ``sum()``, ``isPositive()``, and ``isNegative()`` now ignore non-numeric values consistently.
Expand All @@ -22,7 +24,7 @@ Recent Additions
- ``Config`` adds typed getters (``getString/getInt/getFloat/getBool/getArray/getList/getEnum``), merge/state helpers (``merge/overlay/snapshot/restore/changed``), and ``readonly()`` mode.
- ``Collection`` adds ``immutableProcess()`` / ``pipeImmutable()`` explicit immutable-style pipeline entry.
- ``ArrayKit`` facade adds ``lazyCollection()`` and package now includes ``LazyCollection`` (generator-backed operations).
- New optional helpers: ``ArrayShape`` validator and Laravel-compat layer (``LaravelCompat\\Arr``, ``LaravelCompat\\Collection``).
- New optional helper: ``ArrayShape`` validator.

Compatibility Notes
-------------------
Expand Down
26 changes: 10 additions & 16 deletions docs/rule-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ ArrayKit Facade
public static function helper(): ModuleProxy
public static function dot(): ModuleProxy
public static function config(array $items = []): Config
public static function lazyConfig(string $directory, string $extension = 'php', array $items = []): LazyFileConfig
public static function lazyConfig(string $directory, string $extension = 'php', array $items = [], ?string $namespaceCacheDirectory = null): LazyFileConfig
public static function collection(mixed $data = []): Collection
public static function hookedCollection(mixed $data = []): HookedCollection
public static function lazyCollection(mixed $data = []): LazyCollection
Expand Down Expand Up @@ -514,6 +514,11 @@ Config uses ``BaseConfigTrait``. Public API:
public function reload(array|string $source): bool
public function merge(array $items): bool
public function overlay(array $overlay): bool
public function exportCache(string $path): bool
public function loadCache(string $path): bool
public function readCache(bool $enabled = true): static
public function readCacheEnabled(): bool
public function flushReadCache(): static
public function snapshot(string $name = 'default'): bool
public function restore(string $name = 'default'): bool
public function changed(string $snapshot = 'default'): bool
Expand All @@ -537,6 +542,10 @@ LazyFileConfig loads top-level config files on first keyed access:
public function isLoaded(string $namespace): bool
public function loaded(string $namespace): bool
public function loadedNamespaces(): array
public function namespaceCache(?string $directory): static
public function namespaceCacheDirectory(): ?string
public function warmNamespaceCache(string|array|null $namespaces = null): static
public function flushNamespaceCache(string|array|null $namespaces = null): static
public function all(): array // throws (design choice)

Config Hook-Aware Variants
Expand Down Expand Up @@ -588,18 +597,3 @@ LazyCollection
public function take(int $limit): self
public function takeUntil(callable $callback): self
public function all(): array

Laravel Compatibility
----------------------------------

.. code-block:: php

// Infocyph\ArrayKit\LaravelCompat\Arr
public static function get(iterable $array, string|int|array|null $key = null, mixed $default = null): mixed
public static function set(array &$array, string|array|null $key, mixed $value = null, bool $overwrite = true): bool
public static function has(iterable $array, int|string|array $keys): bool
public static function hasAny(iterable $array, int|string|array $keys): bool
public static function only(iterable $array, array|string $keys): array
public static function except(iterable $array, array|string $keys): array

// Infocyph\ArrayKit\LaravelCompat\Collection extends Collection
21 changes: 5 additions & 16 deletions docs/traits-and-helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -223,20 +223,9 @@ When to Use These Helpers
Laravel Compatibility Layer
---------------------------

ArrayKit also ships optional Laravel-style wrappers:
ArrayKit's default helper surface is namespaced:

- ``Infocyph\ArrayKit\LaravelCompat\Arr``
- ``Infocyph\ArrayKit\LaravelCompat\Collection``

.. code-block:: php

<?php
use Infocyph\ArrayKit\LaravelCompat\Arr;
use Infocyph\ArrayKit\LaravelCompat\Collection as CompatCollection;

$data = ['user' => ['name' => 'Alice']];
Arr::set($data, 'user.role', 'admin');
$name = Arr::get($data, 'user.name');

$c = new CompatCollection([1, 2, 3]);
$all = $c->all();
- ``Infocyph\ArrayKit\array_get``
- ``Infocyph\ArrayKit\array_set``
- ``Infocyph\ArrayKit\collect``
- ``Infocyph\ArrayKit\chain``
1 change: 0 additions & 1 deletion src/Array/ArrayMulti.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ public static function first(array $array, ?callable $callback = null, mixed $de
/**
* @param array<array-key, mixed> $array
* Depth semantics: 0 = unchanged top-level values, 1 = flatten one level, INF = fully flatten.
*
* @return array<array-key, mixed>
*/
public static function flatten(array $array, float|int $depth = \INF): array
Expand Down
12 changes: 3 additions & 9 deletions src/Array/ArraySingle.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public static function avg(array $array): float|int
* @param array<array-key, mixed> $array The array to be chunked.
* @param int $size The size of each chunk.
* @param bool $preserveKeys Whether to preserve the keys in the chunks.
*
* @return array<array-key, mixed> An array of arrays, each representing a chunk of the original array.
*/
public static function chunk(array $array, int $size, bool $preserveKeys = false): array
Expand All @@ -71,7 +70,6 @@ public static function chunk(array $array, int $size, bool $preserveKeys = false
*
* @param array<array-key, mixed> $keys The array of keys.
* @param array<array-key, mixed> $values The array of values.
*
* @return array<array-key, mixed> The combined array.
*/
public static function combine(array $keys, array $values): array
Expand Down Expand Up @@ -199,7 +197,6 @@ public static function duplicates(array $array): array
*
* @param array<array-key, mixed> $array The array to be iterated over.
* @param callable $callback The callback function to apply to each element.
*
* @return array<array-key, mixed> The original array.
*/
public static function each(array $array, callable $callback): array
Expand Down Expand Up @@ -370,7 +367,6 @@ public static function isUnique(array $array): bool
*
* @param array<array-key, mixed> $array The array to be mapped over.
* @param callable $callback The callback function to apply to each element.
*
* @return array<array-key, mixed> The array with each element transformed by the callback.
*/
public static function map(array $array, callable $callback): array
Expand Down Expand Up @@ -548,8 +544,8 @@ public static function nonEmpty(array $array, bool $preserveKeys = false): array
* @param array<array-key, mixed> $array The array to slice.
* @param int $step The "step" value (i.e. the interval between selected elements).
* @param int $offset The offset from which to begin selecting elements.
*
* @return array<array-key, mixed> The sliced array.
*
* @throws InvalidArgumentException If step is less than 1.
*/
public static function nth(array $array, int $step, int $offset = 0): array
Expand Down Expand Up @@ -597,10 +593,9 @@ public static function only(array $array, array|string $keys): array
* @param array<array-key, mixed> $array The array to paginate.
* @param int $page The page number to retrieve (1-indexed).
* @param int $perPage The number of items per page.
* @return array<array-key, mixed> The paginated slice of the array.
*
* @throws InvalidArgumentException If page/per-page are less than 1.
*
* @return array<array-key, mixed> The paginated slice of the array.
*/
public static function paginate(array $array, int $page, int $perPage): array
{
Expand Down Expand Up @@ -766,7 +761,6 @@ public static function same(array $left, array $right, bool $strict = false): bo
* @param array<array-key, mixed> $array The array to search.
* @param mixed $needle The value to search for, or a callable to use for
* searching.
*
* @return int|string|null The key of the value if found, or null if not found.
*/
public static function search(array $array, mixed $needle): int|string|null
Expand All @@ -792,6 +786,7 @@ public static function search(array $array, mixed $needle): int|string|null
*
* @param array<array-key, mixed> $array The array to split.
* @return array<array-key, mixed> A new array containing two child arrays: 'keys' and 'values'.
*
* @example
* $data = ['a' => 1, 'b' => 2, 'c' => 3];
* $keysAndValues = ArraySingle::separate($data);
Expand Down Expand Up @@ -1005,7 +1000,6 @@ public static function values(array $array): array
* This function should take two arguments, the value and the key of each
* element in the array. The function should return true for elements that
* should be kept, and false for elements that should be discarded.
*
* @return array<array-key, mixed> The filtered array.
*/
public static function where(array $array, ?callable $callback = null): array
Expand Down
8 changes: 4 additions & 4 deletions src/Array/ArraySingleOps.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,22 +212,22 @@ public static function symmetricDiff(array $left, array $right, bool $strict): a
public static function unique(array $array, bool $strict): array
{
if (!$strict) {
/** @var array<int, mixed> $unique */
$unique = array_values(array_unique($array, \SORT_REGULAR));
/** @var array<array-key, mixed> $unique */
$unique = array_unique($array, \SORT_REGULAR);

return $unique;
}

$seen = [];
$result = [];
foreach ($array as $item) {
foreach ($array as $key => $item) {
$fingerprint = self::fingerprintStrict($item);
if (isset($seen[$fingerprint])) {
continue;
}

$seen[$fingerprint] = true;
$result[] = $item;
$result[$key] = $item;
}

return $result;
Expand Down
3 changes: 0 additions & 3 deletions src/Array/BaseArrayHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ public static function doReject(array $array, mixed $callback): array
*
* @param array<array-key, mixed> $array The array to search.
* @param callable $callback The callback to use for searching.
*
* @return int|string|null The key of the value if found, or null if not found.
*/
public static function findKey(array $array, callable $callback): int|string|null
Expand Down Expand Up @@ -135,7 +134,6 @@ public static function forget(array &$array, int|string|array $keys): void
*
* @param array<array-key, mixed> $array The array to search.
* @param int|string|array<int, int|string> $keys The key(s) to check for existence.
*
* @return bool True if all the given keys exist in the array, false otherwise.
*/
public static function has(array $array, int|string|array $keys): bool
Expand Down Expand Up @@ -221,7 +219,6 @@ public static function isMultiDimensional(mixed $array): bool
* @param array<array-key, mixed> $array The array from which to retrieve random items.
* @param int|null $number The number of items to retrieve. If null, a single item is returned.
* @param bool $preserveKeys Whether to preserve the keys from the original array.
*
* @return mixed The retrieved item(s) from the array.
*
* @throws InvalidArgumentException If the user requested more items than the array contains.
Expand Down
Loading