xi/collections
Composer 安装命令:
composer require xi/collections
包简介
Functional, immutable and extensible enumerations and collections for PHP 5.3.
关键字:
README 文档
README
Functional, immutable and extensible enumerations and collections for PHP 5.3.
Design Philosophy
PHP has always lacked solid collections support, with the vast majority of programmers making do with arrays and the related built-in functions. With the introduction of SPL in PHP 5.0 and the consequent extensions in 5.3, there are currently more choices than ever if all you want for is speed and answers to specific use cases. Array processing, however, is not significantly better than ten years ago, with the API about as comfortable and handy for everyday tasks as picking at your dinner with a shovel.
Xi Collections aims to rectify the situation and inject your workflow with a hearty dose of functional and declarative aspects. This is intended to result in more clarity in expressing and understanding processing collections of objects or data, allowing you to work faster and deliver more self-documenting code.
Design Principles
-
Object immutability. The collections' methods do not manipulate the collections' contents, but return a new collection instead.
-
API chainability. Most operations will return a new collection except specific reduces and size informations.
-
Embracing functional programming. The Collections API is chosen to facilitate a functional workflow. Existing FP-compatible functionality in PHP is prioritized for inclusion to the API, and other important concepts that are missing are stolen from other languages and libraries.
-
Out-of-the-box extensibility. Decorators for separating the concrete Collection implementations from your modifications to the API are included. The interfaces tend to the minimal rather than extensive, making new implementations easier. Specifically, the whole of PHP's array functions is not built-in, but can be readily used if you need to.
Examples
From imperative to functional
Let's assume a simple loop that filters and transforms a set of data:
public function getMatchingInterestingParts() {
$result = array();
foreach ($this->getFoos() as $key => $value) {
if ($this->match($value)) {
$result[$key] = $value->getInterestingParts();
}
}
return $result;
}
Here's the same expressed with Collections:
public function getMatchingInterestingParts() {
return $this->getFoos()
->filter(function(Foo $foo) {
return $this->matches($foo);
})->map(function(Foo $foo) {
return $foo->getInterestingParts();
});
}
The latter bit of code is not much shorter, and for someone unfamiliar with functional constructs it may be more difficult to process. It does, however, have a few interesting qualities. The code communicates its intent better - filter values, then map the result, and nothing else. This is especially beneficial when considering code with a significantly more complex set of transformations. There's less room for error; index associations are automatically maintained. This also means you can focus on the interesting bits instead of boilerplate, which helps both when reading and when writing the code. A third benefit is that you can take full advantage of type hints and the safety they can bring, something which will be lacking with a simple foreach loop.
Simplifying common access patterns
One of the most common use cases for looping over an array is collecting the results of a member access or method invocation from every item. Collections makes that easy.
public function getBarsByFoos() {
$bars = array();
foreach ($this->getFoos() as $key => $foo) {
$bars[$key] = $foo->getBar();
}
return $bars;
}
// becomes
public function getBarsByFoos() {
return $this->getFoos()->invoke('getBar');
}
public function getFooTrivialities() {
$trivialities = array();
foreach ($this->getFoos() as $key => $foo) {
$trivialities[$key] = $foo->triviality;
}
return $trivialities;
}
// becomes
public function getFooTrivialities() {
return $this->getFoos()->pick('triviality');
}
Picking even works for arrays (or objects implementing ArrayAccess) as well, and you don't need to care about which type the input is.
Inspecting intermediate steps of complex operations
Suppose you have a pipeline where data is transformed according to complex rules.
public function getAliveQuxen() {
return $this->getFoos()
->map($this->fromFooToBar)
->filter(function($bar) { return $bar->isAlive(); })
->map($this->fromBarToQux);
}
Suppose further that you want to inspect the data as it passes from one step to another. This is where you'd introduce temporary variables, were the code imperatively structured. With Collections, all you need is tap. It accepts a function that takes the contents of the collection as its parameter - and does nothing but call the function.
public function getAliveQuxen() {
return $this->getFoos()
->map($this->fromFooToBar)
->filter(function($bar) { return $bar->isAlive(); })
->tap(function($bars) { $this->log($bars); })
->map($this->fromBarToQux);
}
A reader of your code will be able to immediately recognize that the part in tap is only being executed for its side effects and that it has nothing to do with the transformation itself. We could've used each in a similar fashion if we were instead interested in the individual units of computation.
public function getAliveQuxen() {
return $this->getFoos()
->map($this->fromFooToBar)
->filter(function($bar) { return $bar->isAlive(); })
->each(function($bar) { $this->logBar($bar); })
->map($this->fromBarToQux);
}
Delaying computation using views
In some cases you may wish to expose a certain Collection to a consumer, but are not certain whether the Collection is going to be used, and generating one is potentially costly. In such a case you can apply a CollectionView, which is a set of transformation operations that haven't yet been applied to an underlying base Collection. Upon access, the operations will be applied and the resulting values provided to the consumer.
A Collection is transformed into a view backed by itself by invoking view. Best effort is made to apply all Collection method calls lazily. Forcing the view into actual values happens when accessing any Enumerator methods.
public function getEnormouslyExpensiveCollection() {
return $this->getStuff()->view()->map(function(Stuff $s) {
return enormouslyExpensiveComputation($s);
});
}
There's a caveat, however. It is not guaranteed that the transformation from lazy to strict should happen exactly once per CollectionView object. If you need that, you should force the view object to get a strict one.
Using an extended API on the fly
In any given PHP environment there tends to be an amount of existing functionality around for processing data in a Traversable format. PHP itself has a plethora of built-in array functions that aren't feasible to support in the Collection API if it is supposed to be kept minimal. This can potentially change with the introduction of traits in PHP 5.4, but for now you'll have to figure out ways to use these functions manually. At the core of this facility is apply. It accepts a function that applies a transformation of some kind to the collection, the result of which is taken in as a new collection.
Let's assume you want to sort your values. Here's a way to do it using apply.
public function getSortedFoos() {
return $this->getFoos()
->apply(function($collection) {
$foos = $collection->toArray();
ksort($foos);
return $foos;
});
}
The argument is a collection, which will have to be converted to an array first to be accepted by ksort. The function also operates on references, not values, so a temporary variable is necessary. There's an amount of cruft with this use case, but you're likely to be using raw PHP functions rarely. If you're using apply with functions that have a more reasonable API, eg. accept Traversable objects instead of necessitating arrays, the footprint becomes much more palatable. In such a fictional scenario for ksort, for instance:
public function getSortedFoos() {
return $this->getFoos()
->apply('ksort');
}
API basics
Collections has two core interfaces. Enumerable implements a set of collection operations relying only on traversability. Collection extends the Enumerable operations to a superset that includes operations that yield other collections in return. This means collections can be transformed into other collections.
Every concrete class has a static create method that can be used for fluently constructing and accessing a collection. For instance:
ArrayCollection::create($values)->invoke('getBar')->each(function(Bar $bar) { $bar->engage(); });
Below is a short description of the APIs provided by Enumerable and Collection. For more thorough information, you'll need to consult the source.
Enumerable
Element retrieval
first: Returns the first element in the collectionlast: Returns the last element in the collectionfind: Returns the first value that satisfies a given predicate
Element conditions
exists: Checks whether the collection has at least one element satisfying a given predicateforAll: Checks whether all of the elements in the collection satisfy a given predicatecountAll: Counts the amount of elements in the collection that satisfy a given predicate
Size information
count: Counts the amount of elements in the collection
Reduces
reduce: Uses a given callback to reduce the collection's elements to a single value, starting from a provided initial value
Invocations
tap: Calls a provided callback with this object as a parametereach: Performs an operation once per key-value pair
Collection
Maps
map: Applies a callback for each value-key-pair in the Collection and returns a new one with values replaced by the return values from the callbackflatMap: Applies a callback for each key-value-pair in the Collection assuming that the callback result value is iterable and returns a new one with values from those iterablepick: Get a Collection with a key or member property picked from each valuevalues: Get a Collection with just the values from this Collectionkeys: Get a Collection with the keys from this one as valuesinvoke: Map this Collection by invoking a method on every valueapply: Creates a new Collection of this type from the output of a given callback that takes this Collection as its argument
Subcollections
take: Creates a new Collection with up to $number first elements from this onerest: Creates a new Collection with the rest of the elements except firstfilter: Creates a Collection with the values of this collection that match a given predicatefilterNot: Creates a collection with the values of this collection that do not match a given predicateunique: Get a Collection with only the unique values from this one
Subdivisions
partition: Split a collection into a pair of two collections; one with elements that match a given predicate, the other with the elements that do not.groupBy: Group the values in the Collection into nested Collections according to a given callback
Additions
concatenate: Creates a Collection with elements from this and another oneunion: Creates a Collection with key-value pairs in the$otherCollection overriding ones in$thisCollectionflatten: Flatten nested arrays and Traversablesadd: Get a new Collection with given value and optionally key appended
Size information
isEmpty: Checks whether the collection is empty
Sorts
indexBy: Reindex the Collection using a given callbacksortWith: Get this Collection sorted with a given comparison functionsortBy: Get this Collection sorted with a given metric
Views
view: Provides a Collection where transformer operations are applied lazily
Specific reduces
min: Returns the minimum value in the collectionmax: Returns the maximum value in the collectionsum: Returns the sum of values in the collectionproduct: Returns the product of values in the collection
CollectionView
force: Coerces this view back into the underlying Collection type
Collection implementations
ArrayCollection: Basic Collection backed by a plain PHP array.OuterCollection: A decorator for a Collection. Can easily be extended to provide more collection operations without locking down the implementation specifics.
Running the unit tests
phpunit -c tests
TODO
- Collection implementations backed by SPL (SplFixedArray, SplDoublyLinkedList?)
- Use traits?
xi/collections 适用场景与选型建议
xi/collections 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 100.66k 次下载、GitHub Stars 达 26, 最近一次更新时间为 2012 年 10 月 15 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「collections」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 xi/collections 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 xi/collections 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 xi/collections 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
This package provides type-safe extension of the laravel collection, forcing a single type of object.
Doctrine Collections adapter for Rekapager pagination library
Library to mimics generic collections
The helpers allows you to easy use DATE. Change format, add/sub day, diff date, get quarter - all this is available in this library.
Kyanda API Library
Collection classes for the Monolith framework.
统计信息
- 总下载量: 100.66k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 26
- 点击次数: 17
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: BSD-3-Clause
- 更新时间: 2012-10-15