noctud/collection 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

noctud/collection

Composer 安装命令:

composer require noctud/collection

包简介

🗺️ Type-safe, key-preserving, mutable/immutable List/Set/Map for PHP

README 文档

README

Docs codecov Latest Stable Version License: MIT Discord

Type-safe, mutable/immutable, sortable and key-preserving List/Map/Set collections for PHP 8.4+.

composer require noctud/collection

✨ Features

  • Type-safe: Full generics support. Static analyzers understand every element type through the chain.
  • Key-preserving: Map keys like float, bool or "1" retain their original types. No silent type casting.
  • Object keys: Use objects as map keys out of the box. Implement Hashable for custom identity semantics.
  • Mutable & Immutable: Choose the right variant. Immutable methods are marked with #[NoDiscard].
  • Lazy Init: Construct collections from closures. Uses PHP 8.4 lazy objects — materialized only on first access.
  • Interface-driven: Every type is an interface. Factory functions return contracts, not concrete classes.
  • Expressive: Rich set of higher-order functions — map, filter, sorted, flatMap, groupBy, partition, and more.
  • Chainable: Mutating methods return a collection — read result like $set->tracked()->add('a')->changed.
  • Strict: Choose between throwing and nullable methods (get/getOrNull, first/firstOrNull, etc.)
  • Inspired by Kotlin: Factory functions, mutable/immutable split, OrNull conventions, and namings.

🗺️ Architecture

Collection<E>               → Ordered elements, read-only
├── List<E>                 → Indexed, array access
│   ├── MutableList<E>      → Mutating methods (write & sort)
│   └── ImmutableList<E>    → Mutation returns new with #[NoDiscard]
└── Set<E>                  → Unique values, no array access
    ├── MutableSet<E>       → Mutating methods (write & sort)
    └── ImmutableSet<E>     → Mutation returns new with #[NoDiscard]

Map<K,V>                    → Ordered key-value pairs, array access
├── MutableMap<K,V>         → Mutating methods (write & sort)
└── ImmutableMap<K,V>       → Mutation returns new with #[NoDiscard]

Full architecture is shown in docs, there are also Writable interfaces for easy third party implementations.

🏗️ Constructing

Use factory functions from namespace Noctud\Collection.

setOf(['a', 'b']); // ImmutableSet<string>
mutableSetOf(['a', 'b']); // MutableSet<string>

listOf(['a', 'b']); // ImmutableList<string>
mutableListOf(['a', 'b']); // MutableList<string>

mapOf(['a' => 1, 'b' => 2]); // ImmutableMap<string, int>
mutableMapOf(['a' => 1, 'b' => 2]); // MutableMap<string, int>

Use stringMapOf/mutableStringMapOf and intMapOf/mutableIntMapOf for better performance and ~50% less memory — they use single-array storage and enforce key types at runtime.

📖 Accessing

Array access is strict by default — throws on missing keys/indices. Use ?? for safe fallback.

$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

Sets support only the contains method, they have no array access by design.

🌪️ Filtering & transformations

All transformation methods (filter, map, flatMap, zip, partition, ...) always return a new immutable collection, regardless of whether the source is mutable or immutable. Unlike array_filter, Lists are always reindexed — no gaps, no need for array_values().

$set->filter(fn($el) => strlen($el->property) > 3); // new Set<E>
$map->filter(fn($v, $k) => strlen($k->property) > 3); // new Map<K,V>
$map->filterValuesNotNull(); // new Map<K,V> where V is not null
$map->values->filter(fn($v) => $v > 10); // new Collection<V>

📊 Sorting

Every Collection and Map is sequentially ordered, so sorting is supported everywhere.

  • sorted* returns a new collection, sort* sorts in place (Mutable only).
  • *By takes a selector, *With takes a comparator. Add Desc for descending.
// 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 views

Every Map exposes live read-only $keys, $values, and $entries views. These are real Set and Collection objects backed by the same underlying store — mutations to the map are immediately visible through views and vice versa.

$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 }

✔️ Quantifiers

Check if all/any or none of the elements match the predicate.

$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

➰ Iterating

All collections are traversable.

$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");
}

⛓️ Chainable

Mutating methods return $this (Mutable) or a new instance (Immutable). Both share the same API, but immutable methods are marked with #[NoDiscard] to prevent accidental misuse.

$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);

Method tracked() wraps a mutable collection in a proxy that tracks changes. The $changed flag is available on the return value of each mutation method, not on the wrapper itself.

$map = mutableMapOf(['a' => 'b']);
if ($map->tracked()->remove('a')->changed) {
    // do something only if 'a' was actually removed
}

🛡️ Type safety

Mutable collections enforce strict typing — PHPStan warns if you try to add elements of incompatible types. Immutable collections allow type widening since they return a new instance with potentially different types.

// Mutable — strict, PHPStan warns on type mismatch
$map = mutableMapOf(['a' => 1]); // MutableMap<string, int>
$map->put('b', 'wrong'); // ❌ PHPStan error: string is not int

// Immutable — widening allowed, returns new instance
$map = mapOf(['a' => 1]); // ImmutableMap<string, int>
$new = $map->put('b', 'text'); // ✅ ImmutableMap<string, int|string>

🔑 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 will always preserve original keys, you have to only worry about constructing the map.

💤 Lazy Initialization

Construct from a closure — the callback executes only on first access. Under the hood, lazy collections use PHP 8.4's Lazy Objects — the internal store is a ghost proxy materialized only when first accessed.

// 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

Lazy collections behave identically to eager ones — there is no way to tell from outside. Always construct lazy collections using closures, not Generator objects directly.

㊙️ Objects as keys

Use objects as map keys out of the box. By default, objects are hashed using spl_object_id.

$map = mapOfPairs([[$user, 'data']]);
isset($map[$user]); // ✅ True, same object instance
isset($map[clone $user]); // ❌ False, different instance

Implement Hashable for custom identity semantics:

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

🧩 Extending

Every type you interact with is an interface — ImmutableList, MutableMap, Set, even MapEntry. Logic is encapsulated in traits, so you can turn any class into a collection.

For custom stores, database-backed collections, and more, see the Extending guide.

🚀 Performance

This library prioritizes type safety and correctness. Lists and Sets have minimal overhead compared to native arrays. The generic mapOf() uses dual-array storage to preserve any key type, which adds memory and performance overhead.

When keys are exclusively strings or integers, use the optimized variants for maximum performance:

$users = stringMapOf(['alice' => 28, 'bob' => 35]); // or mutableStringMapOf()
$scores = intMapOf([1 => 100, 2 => 85, 3 => 92]); // or mutableIntMapOf()

These use single-array storage, skip key hashing entirely, and use ~50% less memory than mapOf().

Converting between mutable and immutable via toMutable()/toImmutable() uses copy-on-write — the data is shared until either side is modified, making variant switching virtually free.

🔎 Static analysis

Generics are fully supported by PHPStan and Psalm. PhpStorm has known limitations with generics inference.

📚 Documentation

统计信息

  • 总下载量: 650
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 48
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 48
  • Watchers: 1
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-02-15

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固