From 11aa81069f3fbfd18bd00144b5a2e9aa9104824c Mon Sep 17 00:00:00 2001 From: "A. B. M. Mahmudul Hasan" Date: Mon, 13 Apr 2026 09:45:39 +0600 Subject: [PATCH] ArrayKit --- README.md | 19 ++++++ docs/array-helpers.rst | 11 ++++ docs/config.rst | 1 + docs/facade.rst | 61 +++++++++++++++++ docs/index.rst | 1 + docs/quick-usage.rst | 15 +++++ docs/rule-reference.rst | 25 +++++++ src/Array/BaseArrayHelper.php | 2 +- src/ArrayKit.php | 93 ++++++++++++++++++++++++++ src/Facade/ModuleProxy.php | 26 ++++++++ tests/Feature/ArrayKitFacadeTest.php | 98 ++++++++++++++++++++++++++++ 11 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 docs/facade.rst create mode 100644 src/ArrayKit.php create mode 100644 src/Facade/ModuleProxy.php create mode 100644 tests/Feature/ArrayKitFacadeTest.php diff --git a/README.md b/README.md index 7d3495a..917c952 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ real-world PHP projects. - **Dot Notation Get/Set/Flatten** - **Dynamic Config with Hooks** - **Collection & Hooked Collection** +- **Unified Facade (`ArrayKit`)** - **Traits for DTO & Hooking** - **Pipeline for Collection Ops** - **Global Helpers (`functions.php`)** @@ -68,6 +69,11 @@ real-world PHP projects. |-------------------|------------------------------------------------------------| | **functions.php** | Global shortcut functions for frequent array/config tasks. | +### ➤ Facade + +| Class | Description | +|-------------------|-----------------------------------------------------------------------------------------------| +| **ArrayKit** | Single entry point for arrays, dot tools, config, and collections (`single()`, `multi()`, etc.). | ## ✅ Requirements @@ -82,6 +88,19 @@ composer require infocyph/arraykit ## 🚀 Quick Examples +### 🔹 One Facade Entry Point + +```php +use Infocyph\ArrayKit\ArrayKit; + +$isList = ArrayKit::single()->isList([1, 2, 3]); // true +$flat = ArrayKit::multi()->flatten([[1], [2, [3]]]); // [1, 2, 3] +$name = ArrayKit::dot()->get(['user' => ['n' => 'A']], 'user.n'); // A + +$config = ArrayKit::config(['app' => ['env' => 'local']]); +$env = $config->get('app.env'); // local +``` + ### 🔹 Single-Dimensional Helpers ```php diff --git a/docs/array-helpers.rst b/docs/array-helpers.rst index 11f3e64..bdf7fc1 100644 --- a/docs/array-helpers.rst +++ b/docs/array-helpers.rst @@ -7,6 +7,17 @@ ArrayKit ships static helpers grouped by data shape: - ``ArrayMulti`` for nested arrays / row collections - ``BaseArrayHelper`` for lower-level shared operations +If you prefer one entry point, use ``Infocyph\ArrayKit\ArrayKit``: + +.. code-block:: php + + isList([1, 2, 3]); + $flat = ArrayKit::multi()->flatten([[1], [2, [3]]]); + $wrapped = ArrayKit::helper()->wrap('x'); + Choosing the Right Helper ------------------------- diff --git a/docs/config.rst b/docs/config.rst index b5d2948..67e31e3 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -36,6 +36,7 @@ Important behavior: - ``loadArray()`` and ``loadFile()`` only load when config is currently empty. - If already loaded, they return ``false`` and do not overwrite existing items. +- Facade-based config creation is documented in :doc:`facade`. Reading Values -------------- diff --git a/docs/facade.rst b/docs/facade.rst new file mode 100644 index 0000000..a6d29f6 --- /dev/null +++ b/docs/facade.rst @@ -0,0 +1,61 @@ +Facade +====== + +Use ``Infocyph\ArrayKit\ArrayKit`` as a single entry point for the package. + +Classes: + +- ``Infocyph\ArrayKit\ArrayKit`` +- ``Infocyph\ArrayKit\Facade\ModuleProxy`` + +Why this exists +--------------- + +The facade keeps top-level usage consistent while preserving each module's native API. + +Module Entry Points +------------------- + +These methods return a lightweight ``ModuleProxy`` that forwards calls to static module methods. + +.. code-block:: php + + isList([1, 2, 3]); + $flat = ArrayKit::multi()->flatten([[1], [2, [3]]]); + $wrapped = ArrayKit::helper()->wrap('x'); + $name = ArrayKit::dot()->get(['user' => ['name' => 'Alice']], 'user.name'); + +Factory Entry Points +-------------------- + +Use these when you want object instances instead of static helpers. + +.. code-block:: php + + ['env' => 'local']]); + $lazy = ArrayKit::lazyConfig(__DIR__.'/config'); + + $collection = ArrayKit::collection([1, 2, 3]); + $hooked = ArrayKit::hookedCollection(['name' => 'alice']); + $pipeline = ArrayKit::pipeline([1, 2, 3, 4]); + +Behavior Notes +-------------- + +- ``single()``, ``multi()``, ``helper()``, and ``dot()`` return cached proxies. +- Proxy calls map directly to target static methods. +- Calling a missing method via proxy throws ``BadMethodCallException``. + +Related Guides +-------------- + +- Array helpers: :doc:`array-helpers` +- Dot notation: :doc:`dot-notation` +- Collections: :doc:`collection` +- Configuration: :doc:`config` diff --git a/docs/index.rst b/docs/index.rst index a45369f..375ae05 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,7 @@ Contents installation quick-usage + facade array-helpers dot-notation collection diff --git a/docs/quick-usage.rst b/docs/quick-usage.rst index 6baeeac..ab9c3e4 100644 --- a/docs/quick-usage.rst +++ b/docs/quick-usage.rst @@ -3,6 +3,21 @@ Quick Usage This page shows copy-paste examples for common ArrayKit operations. +ArrayKit Facade Example +---------------------- + +.. code-block:: php + + isList([1, 2, 3]); + $flat = ArrayKit::multi()->flatten([[1], [2, [3]]]); + $name = ArrayKit::dot()->get(['user' => ['name' => 'Alice']], 'user.name'); + + $config = ArrayKit::config(['app' => ['env' => 'local']]); + $env = $config->get('app.env'); + ArraySingle Example ------------------- diff --git a/docs/rule-reference.rst b/docs/rule-reference.rst index 139ee18..6cc059f 100644 --- a/docs/rule-reference.rst +++ b/docs/rule-reference.rst @@ -28,6 +28,9 @@ Object-style data pipeline and fluent transformations: Configuration storage with optional get/set hooks: :doc:`config` +Single facade entrypoint for modules and factories: + :doc:`facade` + DTO and hook traits plus global helper functions: :doc:`traits-and-helpers` @@ -95,6 +98,28 @@ Global Helper Functions function collect(mixed $data = []): Collection function chain(mixed $data): Pipeline +ArrayKit Facade +--------------- + +.. code-block:: php + + public static function single(): ModuleProxy + public static function multi(): ModuleProxy + 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 collection(mixed $data = []): Collection + public static function hookedCollection(mixed $data = []): HookedCollection + public static function pipeline(mixed $data): Pipeline + +Facade ModuleProxy +------------------ + +.. code-block:: php + + public function __call(string $method, array $arguments): mixed + BaseArrayHelper --------------------------------------- diff --git a/src/Array/BaseArrayHelper.php b/src/Array/BaseArrayHelper.php index df42292..7af1697 100644 --- a/src/Array/BaseArrayHelper.php +++ b/src/Array/BaseArrayHelper.php @@ -287,7 +287,7 @@ public static function tap(array $array, callable $callback): array * filled with the numbers 1 through $number. * * Example: - * ArrayKit::times(3, function ($i) { + * ArrayKit::helper()->times(3, function ($i) { * return "Row #{$i}"; * }); * // Output: ["Row #1", "Row #2", "Row #3"] diff --git a/src/ArrayKit.php b/src/ArrayKit.php new file mode 100644 index 0000000..885cd36 --- /dev/null +++ b/src/ArrayKit.php @@ -0,0 +1,93 @@ +isList(...) + * - ArrayKit::multi()->flatten(...) + * - ArrayKit::helper()->times(...) + * - ArrayKit::dot()->get(...) + */ +final class ArrayKit +{ + /** + * @var array + */ + private static array $moduleProxies = []; + + private function __construct() {} + + public static function collection(mixed $data = []): Collection + { + return Collection::make($data); + } + + public static function config(array $items = []): Config + { + $config = new Config(); + if ($items !== []) { + $config->loadArray($items); + } + + return $config; + } + + public static function dot(): ModuleProxy + { + return self::proxy(DotNotation::class); + } + + public static function helper(): ModuleProxy + { + return self::proxy(BaseArrayHelper::class); + } + + public static function hookedCollection(mixed $data = []): HookedCollection + { + return HookedCollection::make($data); + } + + public static function lazyConfig(string $directory, string $extension = 'php', array $items = []): LazyFileConfig + { + return new LazyFileConfig($directory, $extension, $items); + } + + public static function multi(): ModuleProxy + { + return self::proxy(ArrayMulti::class); + } + + public static function pipeline(mixed $data): Pipeline + { + return Collection::make($data)->process(); + } + + public static function single(): ModuleProxy + { + return self::proxy(ArraySingle::class); + } + + /** + * @param class-string $className + */ + private static function proxy(string $className): ModuleProxy + { + return self::$moduleProxies[$className] ?? self::$moduleProxies[$className] = new ModuleProxy($className); + } +} diff --git a/src/Facade/ModuleProxy.php b/src/Facade/ModuleProxy.php new file mode 100644 index 0000000..56829d7 --- /dev/null +++ b/src/Facade/ModuleProxy.php @@ -0,0 +1,26 @@ +targetClass, $method)) { + throw new BadMethodCallException("Method {$this->targetClass}::{$method} does not exist."); + } + + return $this->targetClass::$method(...$arguments); + } +} diff --git a/tests/Feature/ArrayKitFacadeTest.php b/tests/Feature/ArrayKitFacadeTest.php new file mode 100644 index 0000000..d1ee1d6 --- /dev/null +++ b/tests/Feature/ArrayKitFacadeTest.php @@ -0,0 +1,98 @@ +configPath = sys_get_temp_dir() + . DIRECTORY_SEPARATOR + . 'arraykit-facade-' + . uniqid('', true); + + mkdir($this->configPath, 0777, true); +}); + +afterEach(function () { + arrayKitDeleteDirectory($this->configPath); +}); + +it('exposes array modules through one facade', function () { + $single = ArrayKit::single(); + + expect($single)->toBeInstanceOf(ModuleProxy::class) + ->and(ArrayKit::single())->toBe($single) + ->and($single->isList([1, 2, 3]))->toBeTrue() + ->and(ArrayKit::multi()->flatten([[1], [2, [3]]]))->toBe([1, 2, 3]) + ->and(ArrayKit::helper()->wrap('x'))->toBe(['x']) + ->and(ArrayKit::dot()->get(['a' => ['b' => 1]], 'a.b'))->toBe(1); +}); + +it('creates config instances from the facade', function () { + $config = ArrayKit::config(['app' => ['name' => 'ArrayKit']]); + + expect($config)->toBeInstanceOf(Config::class) + ->and($config->get('app.name'))->toBe('ArrayKit'); +}); + +it('creates lazy config instances from the facade', function () { + arrayKitWriteArrayFile($this->configPath, 'db', ['host' => 'localhost']); + + $config = ArrayKit::lazyConfig($this->configPath); + + expect($config)->toBeInstanceOf(LazyFileConfig::class) + ->and($config->get('db.host'))->toBe('localhost'); +}); + +it('creates collection and pipeline helpers from the facade', function () { + $collection = ArrayKit::collection([1, 2, 3]); + $hooked = ArrayKit::hookedCollection(['name' => 'alice']); + $sum = ArrayKit::pipeline([1, 2, 3])->sum(); + + expect($collection)->toBeInstanceOf(Collection::class) + ->and($hooked)->toBeInstanceOf(HookedCollection::class) + ->and($sum)->toBe(6); +});