componenta / filter
Composable predicates and collection filters for Componenta
Requires
- php: ^8.4
- ext-ctype: *
- ext-filter: *
- componenta/arrayable: ^1.0
- componenta/iterator: ^1.1.0
Requires (Dev)
- pestphp/pest: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-13 23:21:32 UTC
README
Composable predicates and iterable collection filters for PHP 8.4+.
Installation
composer require componenta/filter
Requirements
- PHP 8.4+
componenta/arrayablecomponenta/iterator
Contracts
Version 2 separates single-value predicates from collection operations.
PredicateInterfaceexposes onlyaccept()and is used when one value can be evaluated without collection context.CollectionFilterInterfaceexposes collection processing throughgetIterator(),withIterable(), andtoArray().FilterInterfaceextends both interfaces and represents a predicate-backed collection filter.AbstractCollectionFilterprovides immutable iterable binding andtoArray()for collection-only operators.AbstractFilterextendsAbstractCollectionFilterand adds predicate-backed iteration.
Predicate-backed Filters
use Componenta\Filter\StringFilter; $filter = new StringFilter(['one', 2, 'three']); $filter->accept('value'); // true $filter->toArray(); // ['one', 'three']
String-oriented predicates accept only string and Stringable values. Scalars such as integers and booleans are not silently cast to strings.
Pure Predicates and Composition
Custom predicates do not need iterable behavior:
use Componenta\Filter\ChainableFilter; use Componenta\Filter\PredicateInterface; $positiveInteger = new class implements PredicateInterface { public function accept(mixed $value, string|int|null $key = null): bool { return is_int($value) && $value > 0; } }; $filter = new ChainableFilter($positiveInteger); $filter->accept(10); // true
Filterable, ChainableFilter, OneOfFilter, NotFilter, and RecursiveFilter depend on PredicateInterface. Filterable/ChainableFilter use AND semantics; OneOfFilter uses OR semantics and an empty OneOfFilter rejects every value.
Collection-only Operators
The following operators intentionally do not implement PredicateInterface:
PercentageFilter: the result depends on collection size and position.UniqueFilter: uniqueness depends on values already observed in the current traversal.MergingFilter: concatenates results from multipleCollectionFilterInterfaceinstances.
use Componenta\Filter\IntFilter; use Componenta\Filter\MergingFilter; use Componenta\Filter\PercentageFilter; use Componenta\Filter\UniqueFilter; $filter = new MergingFilter( new IntFilter([1, 'two']), new PercentageFilter(50, ['a', 'b', 'c', 'd']), new UniqueFilter([1, 1, 2]), ); $filter->toArray(); // [1, 'a', 'b', 1, 2]
MergingFilter::withIterable() uses ReplayableIterator to fan out one-shot sources lazily. Binding a generator does not consume it immediately, and each inner filter gets an independent replay cursor.
Exact Numeric Predicates
NumericFilter, BetweenFilter, RangeFilter, GreaterThan*, LessThan*, MultipleOfFilter, and the parity filters do not collapse numeric strings to float unnecessarily.
Large integers, long decimal strings, and scientific notation can therefore be compared without the 2^53 precision loss of IEEE-754 conversion. NAN and infinite values are rejected by value-oriented numeric predicates. Actual PHP float inputs keep their native floating-point precision; MultipleOfFilter uses a bounded tolerance only when the tested value itself is a float.
Date Ranges
DateRangeFilter accepts DateTimeInterface or absolute ISO-like date strings. It captures a timezone at construction for local strings, preserves microsecond precision, and does not accept relative expressions such as tomorrow or silently normalized impossible dates.
$range = new DateRangeFilter( '2026-01-01 00:00:00', '2026-12-31 23:59:59', timezone: new DateTimeZone('Europe/Copenhagen'), );
Recursive Filtering
RecursiveFilter detects iterable-object cycles and also enforces maxDepth (default 64) so recursive arrays cannot recurse indefinitely.
$filter = new RecursiveFilter($predicate, maxDepth: 32);
Random Filtering
RandomFilter uses an isolated Random\Randomizer instead of the process-global mt_rand() state. Inject a Randomizer when deterministic seeded behavior is needed.
Validation
Invalid configuration is rejected early with InvalidArgumentException where safe execution would otherwise be impossible. This includes invalid numeric/date ranges, regular expressions, filter_var() IDs/options, probabilities, percentages, and typed class/string/key lists.
Breaking Changes from 1.x
FilterInterfaceis now the intersection ofPredicateInterfaceandCollectionFilterInterface.PercentageFilter,UniqueFilter, andMergingFilterare collection-only and have noaccept()method.- Predicate composition APIs accept
PredicateInterface; custom predicates no longer need iterable methods. MergingFilteraccepts arbitraryCollectionFilterInterfaceimplementations and replays one-shot inputs lazily.- String predicates no longer cast arbitrary scalars to strings.
- Numeric range thresholds support
int|float|stringand exact decimal/scientific comparison. DateRangeFilteruses absolute deterministic parsing, a captured timezone, and microsecond precision.RandomFilterno longer consumes globalmt_rand()state.RecursiveFilterhas bounded recursion and cycle detection.
Development
composer install
composer test
CI validates Composer metadata, lints all PHP sources and tests, and runs Pest on PHP 8.4 and 8.5. A manual mutation-audit workflow is available for checking test sensitivity.