noctud/collection

GitHub: noctud/collection

为 PHP 8.4+ 提供类型安全、可变/不可变分离、键类型保留的 List/Map/Set 集合库,灵感源自 Kotlin。

Stars: 43 | Forks: 0

# Noctud 集合 适用于 **PHP 8.4+** 的类型安全、可变/不可变、可排序且保留键类型的 List/Map/Set 集合。 [![Docs](https://img.shields.io/badge/docs-noctud.dev-8A2BE2)](https://noctud.dev/collection/getting-started) [![codecov](https://codecov.io/gh/noctud/collection/branch/0.1.x/graph/badge.svg)](https://codecov.io/gh/noctud/collection) [![Latest Stable Version](https://img.shields.io/packagist/v/noctud/collection.svg)](https://packagist.org/packages/noctud/collection) ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) [![Discord](https://img.shields.io/badge/discord-join-5865F2?logo=discord&logoColor=white)](https://discord.gg/jS3fKe6vW9) ``` composer require noctud/collection ``` ## ✨ 特性 - **类型安全 (Type-safe)**:完整的泛型支持。静态分析器能够理解链式调用中每个元素的类型。 - **保留键类型 (Key-preserving)**:Map 的键如 `float`、`bool` 或 `"1"` 会保留其原始类型。没有隐式类型转换。 - **对象键 (Object keys)**:开箱即用地支持对象作为 Map 键。实现 `Hashable` 接口可自定义身份语义。 - **可变 & 不可变 (Mutable & Immutable)**:选择合适的变体。不可变方法标记有 `#[NoDiscard]`。 - **延迟初始化 (Lazy Init)**:通过闭包构建集合。利用 PHP 8.4 的延迟对象 —— 仅在首次访问时实体化。 - **接口驱动 (Interface-driven)**:每个类型都是一个接口。工厂函数返回契约,而非具体类。 - **表达力强 (Expressive)**:丰富的更高阶函数集 —— map、filter、sorted、flatMap、groupBy、partition 等。 - **可链式调用 (Chainable)**:变更方法返回集合 —— 像这样读取结果 `$set->tracked()->add('a')->changed`。 - **严格 (Strict)**:可选择抛出异常或返回空值的方法(`get`/`getOrNull`、`first`/`firstOrNull` 等)。 - **灵感源自 Kotlin**:工厂函数、可变/不可变分离、`OrNull` 约定以及命名风格。 ## 🗺️ 架构 ``` Collection → Ordered elements, read-only ├── List → Indexed, array access │ ├── MutableList → Mutating methods (write & sort) │ └── ImmutableList → Mutation returns new with #[NoDiscard] └── Set → Unique values, no array access ├── MutableSet → Mutating methods (write & sort) └── ImmutableSet → Mutation returns new with #[NoDiscard] Map → Ordered key-value pairs, array access ├── MutableMap → Mutating methods (write & sort) └── ImmutableMap → Mutation returns new with #[NoDiscard] ``` 完整架构[见文档](https://noctud.dev/collection/getting-started#architecture),此外还有 Writable 接口以便于第三方实现。 ### 🏗️ 构建 使用命名空间 `Noctud\Collection` 下的[工厂函数](https://noctud.dev/collection/api/functions)。 ``` setOf(['a', 'b']); // ImmutableSet mutableSetOf(['a', 'b']); // MutableSet listOf(['a', 'b']); // ImmutableList mutableListOf(['a', 'b']); // MutableList mapOf(['a' => 1, 'b' => 2]); // ImmutableMap mutableMapOf(['a' => 1, 'b' => 2]); // MutableMap ``` 使用 `stringMapOf`/`mutableStringMapOf` 和 `intMapOf`/`mutableIntMapOf` 可获得更好的性能并减少约 50% 的内存占用 —— 它们使用单数组存储并在运行时强制键类型。 ### 📖 访问 数组访问默认是严格的 —— 键/索引不存在时抛出异常。使用 `??` 进行安全回退。 ``` $list[0]; // throws if missing, get() $list[0] ?? null; // null if missing $list->getOrNull(0); // null if missing $list->firstOrNull(); // null if empty ``` ``` $map['key']; // throws if missing, get() $map['key'] ?? null; // null if missing $map->getOrNull('key'); // null if missing $map->values->first(); // throws if empty ``` Set 仅支持 `contains` 方法,设计上没有数组访问功能。 ### 🌪️ 过滤与转换 所有转换方法(`filter`、`map`、`flatMap`、`zip`、`partition` 等)始终返回一个新的**不可变**集合,无论源集合是可变还是不可变。与 `array_filter` 不同,List 总是重新索引 —— 没有间隙,无需调用 `array_values()`。 ``` $set->filter(fn($el) => strlen($el->property) > 3); // new Set $map->filter(fn($v, $k) => strlen($k->property) > 3); // new Map $map->filterValuesNotNull(); // new Map where V is not null $map->values->filter(fn($v) => $v > 10); // new Collection ``` ### 📊 排序 每个 Collection 和 Map 都是按顺序排序的,因此到处都支持[排序](https://noctud.dev/collection/sorting)。 - `sorted*` 返回一个新集合,`sort*` 原地排序(仅限 Mutable)。 - `*By` 接受选择器,`*With` 接受比较器。添加 `Desc` 表示降序。 ``` // Basic $list->sort(); // also sortDesc() $map->sortByKey(); // also sortByValue() // Selector examples $list->sortBy(fn ($v) => $v->score); $map->sortByKeyDesc(fn ($k) => strlen($k)); // Comparator examples (advanced use cases) $list->sortWith(fn ($a, $b) => $b->score <=> $a->score); $map->sortWithKey(fn ($a, $b) => $a <=> $b); // also sortWithValue() $map->sortWith(fn (MapEntry $a, MapEntry $b) => $a->value <=> $b->value); ``` ### 👁️ Map 视图 每个 Map 都暴露实时的只读 [`$keys`、`$values` 和 `$entries`](https://noctud.dev/collection/map#views) 视图。这些是 由同一底层存储支持的真实 `Set` 和 `Collection` 对象 —— 对 Map 的修改会立即反映在视图中,反之亦然。 ``` $map = mapOf(['alice' => 28, 'bob' => 35, 'carol' => 22]); $map->values->min(); // 22 $map->keys->filter(fn($k) => strlen($k) > 3); // Set {'alice', 'carol'} $map->entries->first(); // MapEntry { key: 'alice', value: 28 } ``` ### ✔️ 量词 检查是否所有、任意或没有元素匹配谓词。 ``` $set->all(fn($v) => strlen($v->property) > 3); // true|false $map->any(fn($v, $k) => strlen($k->property) > 3); // true|false $map->values->none(fn($v) => $v->isActive); // true|false ``` ### ➰ 迭代 所有集合都是可遍历的。 ``` $set->forEach(fn($v) => print("$v->property\n")); $map->forEach(fn($v, $k) => print("$k = $v\n")); // Keys for Sets are generated on the fly (0, 1, 2, ...) foreach ($collection as $k => $v) { print("$k = $v\n"); } ``` ### ⛓️ 可链式调用 变更方法返回 `$this`(Mutable)或新实例(Immutable)。两者共享相同的 API,但不可变方法标记有 `#[NoDiscard]` 以防止意外误用。 ``` $new = $map->put('b', 2) ->remove('a') ->filter(fn($v, $k) => $v > 1) ->mapValues(fn($v, $k) => $v * 2) ->sortedByKey(); $mutableSet->clear() ->addAll(['a', 'b', 'c', null]) ->removeIf(fn($v) => $v === null); ``` 方法 [`tracked()`](https://noctud.dev/collection/mutability#change-tracking) 将可变集合封装在一个跟踪更改的代理中。`$changed` 标志位于每个变更方法的返回值上,而非封装器本身。 ``` $map = mutableMapOf(['a' => 'b']); if ($map->tracked()->remove('a')->changed) { // do something only if 'a' was actually removed } ``` ### 🛡️ [类型安全](https://noctud.dev/collection/mutability) 可变集合强制执行严格类型 —— 如果您尝试添加不兼容类型的元素,PHPStan 会发出警告。 不可变集合允许类型拓宽,因为它们返回一个可能具有不同类型的新实例。 ``` // Mutable — strict, PHPStan warns on type mismatch $map = mutableMapOf(['a' => 1]); // MutableMap $map->put('b', 'wrong'); // ❌ PHPStan error: string is not int // Immutable — widening allowed, returns new instance $map = mapOf(['a' => 1]); // ImmutableMap $new = $map->put('b', 'text'); // ✅ ImmutableMap ``` ### 🔑 [保留键类型](https://noctud.dev/collection/map#preserving-key-types) ``` $map = mutableMapOf(['1' => 'a']); // ❌ Key '1' will be cast to int(1) before the map is created $map = mutableMapOfPairs([['1', 'a']]); // ✅ Key '1' will stay as a string $map['2'] = 'b'; // ✅ Key '2' will stay as string // Enforce string keys (int are only allowed at construction time) $map = stringMapOf(['1' => 'a', 2 => 'b']); // ✅ Keys '1' and '2' will be strings // Constructing from a generator $map = mapOf((function() { yield '1' => 'a'; // ✅ Key '1' will stay as a string })()); ``` Map 将始终保留原始键,您只需关注如何构建 Map。 ### 💤 [延迟初始化](https://noctud.dev/collection/lazy-collections) 通过闭包构建 —— 回调仅在首次访问时执行。底层上,延迟集合使用 PHP 8.4 的 [Lazy Objects](https://www.php.net/manual/en/language.oop5.lazy-objects.php) —— 内部存储是一个仅当首次访问时才实体化的幽灵代理。 ``` // The query runs only if $users is actually read $template->users = listOf(fn() => $repository->getAllUsers()); $lazyMap = mapOf(fn () => ['a' => 1]); // ✅ Good, callback returning an array $lazyMap = mapOf(fn () => $generator); // ✅ Good, callback returning Generator $lazyMap->values; // still lazy, no code executed yet $lazyMap->count(); // first read - executes the callback, materializes the map ``` 延迟集合的行为与急切集合完全相同 —— 从外部无法区分。始终使用闭包构建延迟集合,而非直接使用 Generator 对象。 ### ㊙️ [对象作为键](https://noctud.dev/collection/map#objects-as-keys) 开箱即用地使用对象作为 Map 键。默认情况下,对象使用 `spl_object_id` 进行哈希。 ``` $map = mapOfPairs([[$user, 'data']]); isset($map[$user]); // ✅ True, same object instance isset($map[clone $user]); // ❌ False, different instance ``` 实现 `Hashable` 以自定义身份语义: ``` class User implements \Noctud\Collection\Hashable { public function identity(): string|int { return "user_$this->id"; } } $map = mutableMapOf(); $map[$user] = 'cacheData'; isset($map[clone $user]); // ✅ True, same user ID ``` ### 🧩 扩展 您交互的每个类型都是接口 —— `ImmutableList`、`MutableMap`、`Set`,甚至 `MapEntry`。逻辑封装在 trait 中,因此您可以将任何类转变为集合。 有关自定义存储、数据库支持的集合等,请参阅[扩展指南](https://noctud.dev/collection/extending)。 ### 🚀 性能 本库优先考虑类型安全和正确性。与原生数组相比,List 和 Set 的开销极小。通用的 `mapOf()` 使用双数组存储以保留任何键类型,这增加了内存和性能开销。 当键仅为字符串或整数时,请使用优化变体以获得最大性能: ``` $users = stringMapOf(['alice' => 28, 'bob' => 35]); // or mutableStringMapOf() $scores = intMapOf([1 => 100, 2 => 85, 3 => 92]); // or mutableIntMapOf() ``` 这些变体使用单数组存储,完全跳过键哈希,并且内存使用量比 `mapOf()` 减少约 50%。 通过 `toMutable()`/`toImmutable()` 在可变和不可变之间转换使用写时复制 —— 数据在任意一方修改前都是共享的,使得变体切换几乎无成本。 ### 🔎 静态分析 PHPStan 和 Psalm 完全支持泛型。PhpStorm 在泛型推断方面存在已知的局限性。 ## 📚 文档 - [入门指南](https://noctud.dev/collection/getting-started) — 安装、架构、基本用法 - [List](https://noctud.dev/collection/list) / [Set](https://noctud.dev/collection/set) / [Map](https://noctud.dev/collection/map) — 带示例的类型指南 - [可变性](https://noctud.dev/collection/mutability) — Mutable vs immutable、变更跟踪、写时复制 - [排序](https://noctud.dev/collection/sorting) — 完整排序参考及速查表 - [延迟集合](https://noctud.dev/collection/lazy-collections) — 延迟初始化 - [扩展](https://noctud.dev/collection/extending) — 自定义实现、存储、traits - [API 参考](https://noctud.dev/collection/api/collection) — 所有方法签名
标签:Composer 包, List, Map, Noctud, OpenVAS, PHP, PHP 8.4, Set, Web 开发, 不可变, 云安全监控, 函数式编程, 可变, 后端开发, 开源库, 懒加载, 搜索引擎爬虫, 数据结构, 泛型, 类型安全, 链式调用, 键保留, 集合, 静态分析