noctud/collection
GitHub: noctud/collection
为 PHP 8.4+ 提供类型安全、可变/不可变分离、键类型保留的 List/Map/Set 集合库,灵感源自 Kotlin。
Stars: 43 | Forks: 0
# Noctud 集合
适用于 **PHP 8.4+** 的类型安全、可变/不可变、可排序且保留键类型的 List/Map/Set 集合。
[](https://noctud.dev/collection/getting-started)
[](https://codecov.io/gh/noctud/collection)
[](https://packagist.org/packages/noctud/collection)

[](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 开发, 不可变, 云安全监控, 函数式编程, 可变, 后端开发, 开源库, 懒加载, 搜索引擎爬虫, 数据结构, 泛型, 类型安全, 链式调用, 键保留, 集合, 静态分析