From 3bf0d7e5e481f5bc91a715d0169b47a4b1079180 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 18 Mar 2026 09:03:42 +0100 Subject: [PATCH 01/11] Run CI on support branches, all PRs, and on demand Potential bugfix releases for maintained older versions should receive the same PHP checks as the default branch. Extend push builds to also cover `support/*` and drop the pull request target filter so validation still runs for pull requests merged through other pull requests. Enable `workflow_dispatch` for ad-hoc verification. --- .github/workflows/php.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index bcedb22..03a23d2 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -4,9 +4,9 @@ on: push: branches: - main + - support/* pull_request: - branches: - - main + workflow_dispatch: jobs: php: From 7099cce973dc51af4d2023427ed299849d9dc486 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 18 Mar 2026 09:11:17 +0100 Subject: [PATCH 02/11] Normalize `composer.json` and enable sorted package lists Enable Composer's `sort-packages` setting and run `composer normalize` to establish a consistent structure for `composer.json`. This keeps future dependency changes deterministic and reduces unnecessary diff noise. --- composer.json | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 2bb9df3..626ea5c 100644 --- a/composer.json +++ b/composer.json @@ -1,22 +1,27 @@ { "name": "ipl/stdlib", "description": "ipl Standard Library", - "type": "library", "license": "MIT", + "type": "library", + "require": { + "php": ">=8.2", + "ext-openssl": "*", + "evenement/evenement": "^3.0.1" + }, "autoload": { - "files": ["src/functions_include.php"], "psr-4": { "ipl\\Stdlib\\": "src" - } + }, + "files": [ + "src/functions_include.php" + ] }, "autoload-dev": { "psr-4": { "ipl\\Tests\\Stdlib\\": "tests" } }, - "require": { - "php": ">=8.2", - "ext-openssl": "*", - "evenement/evenement": "^3.0.1" + "config": { + "sort-packages": true } } From 953d54f91aab9aa88540c6bd86124aaa8d25488e Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 18 Mar 2026 09:18:54 +0100 Subject: [PATCH 03/11] Keep `git archive` outputs free of repository-only files Add a minimal `.gitattributes` file so `git archive` excludes CI and Git configuration files as well as test-related files. This keeps release archives focused on the package contents. --- .gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d8bac3e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Exclude Git and CI configuration files from archives. +.git* export-ignore + +# Exclude test-related files from archives. +tests/ export-ignore +phpstan* export-ignore From c4b72e7bb6e1a7e5c0c146a582335259ca566339 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 18 Mar 2026 09:26:33 +0100 Subject: [PATCH 04/11] Clean up `.gitignore` to only cover project-specific artifacts Remove the broad hidden file exclusion and its `.git*` whitelist in favor of a global `~/.gitignore`. Editor and OS artifacts such as `.DS_Store`, `.idea/`, or `.vscode/` are environment-specific and not the project's responsibility to exclude. Each developer should maintain a global ignore file for such artifacts: git config --global core.excludesFile ~/.gitignore Keep only Composer installation artifacts with a comment clarifying why `composer.lock` is excluded for library projects. --- .gitignore | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 34d585a..681b17e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,5 @@ -# Exclude all hidden files -.* - -# Except those related to Git (and GitHub) -!.git* - -# Exclude files from composer install -vendor/ +# Ignore Composer installation artifacts; composer.lock is intentionally +# excluded as this is a library - applications depending on this package +# manage their own lock file. +/vendor/ composer.lock From 942d13a144c8b50dfb4837c3017b90a4f6908fff Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 18 Mar 2026 09:34:08 +0100 Subject: [PATCH 05/11] Convert license file to `LICENSE.md` Rename the license file to `LICENSE.md`, switch to a Markdown heading, and normalize the Icinga URL. This aligns the license file with the repository's Markdown-based documentation and establishes `LICENSE.md` as a consistent convention across all Icinga repositories. --- LICENSE => LICENSE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename LICENSE => LICENSE.md (93%) diff --git a/LICENSE b/LICENSE.md similarity index 93% rename from LICENSE rename to LICENSE.md index 58005ec..564413b 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,6 +1,6 @@ -The MIT License +# MIT License -Copyright (c) 2018 Icinga GmbH https://www.icinga.com +Copyright (c) 2018 Icinga GmbH https://icinga.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 0ec8fbd85f01fbac8b0b98cb83a578baeaf1b7e1 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 18 Mar 2026 09:42:51 +0100 Subject: [PATCH 06/11] Add `CHANGELOG.md` documenting all notable changes Introduced changelog covers all releases and unreleased changes, derived from tags, release notes, commits, and related pull requests. --- CHANGELOG.md | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7be5bb5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,121 @@ +# Changelog + +All notable changes to this library are documented in this file. + +## [Unreleased] + +- **Breaking** Raise minimum PHP version to 8.2 (#63) +- Add strict type declarations (#63) +- Support PHP 8.5 (#61) + +## [v0.14.0] — 2024-07-15 + +- **Breaking** Restrict `Str::symmetricSplit()`'s `$default` parameter to `?string` (#55) +- Support PHP 8.3 + +## [v0.13.0] — 2023-09-21 + +- **Breaking** `Seq` no longer treats function names as needles (#42) +- Add `iterable_value_first()` (#49) +- Add `yield_groups()` to group sorted iterators by callback-derived criteria (#46) +- Fix `Str::symmetricSplit()` to return padded array when subject is `null` (#45) +- Support PHP 8.2 (#39) + + +## [v0.12.1] — 2022-12-13 + +- Refine `Str` method signatures and `null` handling to improve PHP 8.1 compatibility (#37) + +## [v0.12.0] — 2022-06-15 + +- **Breaking** Raise the minimum supported PHP version to 7.2 (#29) +- **Breaking** `Filter::equal()` and `Filter::unequal()` no longer perform wildcard matching; + use `Filter::like()` and `Filter::unlike()` instead (#33) +- **Breaking** Remove mutator and closure support from `Properties` (#31) +- Add iteration support to `Properties` via `IteratorAggregate` (#32) +- Support PHP 8.1 (#29) + +## [v0.11.0] — 2022-03-23 + +- **Breaking** Drop PHP 5.6 support; require PHP ≥ 7.0 (#28) +- Introduce `BaseFilter` trait for simple filter-aware classes (#28) + +## [v0.10.0] — 2021-11-10 + +- Add the `Seq` utility for searching iterables by key or value (#26) +- Fix `Properties` to not mutate values on set (#23); resolve closures before + mutation (#24) +- Support PHP 8 (#21) + +## [v0.9.0] — 2021-03-19 + +- **Breaking** `Filter\Chain` and `Filter\Condition` no longer tolerate dynamic properties; + use dedicated metadata instead (#20) +- Introduce `Filterable` contract interface and `Filters` trait for + filter-aware objects (#19) +- Introduce `Data` class for arbitrary key-value metadata storage (#20) +- Introduce `MetaDataProvider` interface for filter rule metadata (#20) +- Extend `Filter\Chain` with `insertBefore()` and `insertAfter()` (#20) + +## [v0.8.0] — 2021-01-14 + +- Introduce filter system: `Filter` facade and `Filter\Rule`, `Filter\Chain` + (`All`, `Any`, `None`), and `Filter\Condition` (`Equal`, `Unequal`, + `GreaterThan`, `GreaterThanOrEqual`, `LessThan`, `LessThanOrEqual`) (#15) +- Add `random_bytes()` polyfill for PHP < 7.0 (#18) + +## [v0.7.0] — 2020-10-19 + +- Introduce `Properties` trait for dynamic typed property storage (#16) + +## [v0.6.0] — 2020-10-12 + +- Introduce `Translator` contract interface (#14) + +## [v0.5.0] — 2020-03-12 + +- **Breaking** Raise the minimum supported PHP version to 5.6 (#12) +- **Breaking** Refactor contracts and traits: rename `EventEmitter` → `Events`, + `MessageContainer` → `Messages`; supersede `PaginationInterface` with + `Paginatable`; supersede `ValidatorInterface` with `Validator` (#12) +- Introduce new `PluginLoader` interface and `Plugins` trait (#12) +- Introduce `PriorityQueue` class wrapping `SplPriorityQueue` with stable + ordering (#13) + +## [v0.4.0] — 2020-03-10 + +- Add `iterable_key_first()` for retrieving the first key from any iterable (#10) +- Add `Str::symmetricSplit()`, `Str::trimSplit()`, and `Str::startsWith()` + +## [v0.3.0] — 2019-10-16 + +- Introduce `PaginationInterface` (#5) +- Introduce `Str` class with `camel()` case conversion +- Add `is_iterable()` polyfill for PHP < 7.1 + +## [v0.2.0] — 2019-05-16 + +- Introduce `EventEmitter` trait, wrapping + [Evenement](https://github.com/igorw/evenement) (#8) + +## [v0.1.0] — 2019-03-26 + +Initial release providing `AutoloadingPluginLoader`, `MessageContainer` trait, +`ValidatorInterface`, and type/iterable utility functions. + +[Unreleased]: https://github.com/Icinga/ipl-stdlib/compare/v0.14.0...HEAD +[v0.14.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.14.0 +[v0.13.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.13.0 +[v0.12.1]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.12.1 +[v0.12.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.12.0 +[v0.11.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.11.0 +[v0.10.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.10.0 +[v0.9.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.9.0 +[v0.8.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.8.0 +[v0.7.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.7.0 +[v0.6.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.6.0 +[v0.5.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.5.0 +[v0.4.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.4.0 +[v0.3.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.3.0 +[v0.2.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.2.0 +[v0.1.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.1.0 From ba06fc5fe4c58899f4080bc388b1f667672d84f6 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 18 Mar 2026 09:51:29 +0100 Subject: [PATCH 07/11] Expand `README.md` with usage-oriented documentation Add a package overview, installation requirements, and focused examples for the filter system, Events trait, and utility functions. This gives users practical guidance without turning the README into a component-by-component reference. --- README.md | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 220 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 536abf3..ac94294 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,222 @@ # Icinga PHP Library - Standard Library -This is the Stdlib prototype for the Icinga PHP library ([ipl](https://github.com/Icinga/ipl)). -Please do not use this for anything important yet, as all APIs, Interfaces and -paths are still subject to change. +`ipl/stdlib` provides reusable building blocks for Icinga PHP libraries and +applications. It covers declarative filtering, event emission, string and +iterable utilities, lightweight data and message containers, and a +stable priority queue. + +## Installation + +The recommended way to install this library is via +[Composer](https://getcomposer.org): + +```shell +composer require ipl/stdlib +``` + +`ipl/stdlib` requires PHP 8.2 or later with the `openssl` extension. + +## Usage + +### Filter Rows With Declarative Rules + +Build composable filter trees with `ipl\Stdlib\Filter` and evaluate them +against arrays or objects: + +```php +use ipl\Stdlib\Filter; + +$filter = Filter::all( + Filter::equal('problem', '1'), + Filter::none(Filter::equal('handled', '1')), + Filter::like('service', 'www.*') +); + +$row = [ + 'problem' => '1', + 'handled' => '0', + 'service' => 'www.icinga.com', +]; + +if (Filter::match($filter, $row)) { + // The row matches the rule set. +} +``` + +Available condition factories: `equal`, `unequal`, `like`, `unlike`, +`greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`. +Available logical factories: `all`, `any`, `none`, `not`. + +### Build Filters Incrementally + +When an object needs to collect filter conditions over time, use the `Filters` +trait. It complements the `Filterable` contract and exposes `filter()`, +`orFilter()`, `notFilter()`, and `orNotFilter()`: + +```php +use ipl\Stdlib\Contract\Filterable; +use ipl\Stdlib\Filter; +use ipl\Stdlib\Filters; + +class Query implements Filterable +{ + use Filters; +} + +$query = (new Query()) + ->filter(Filter::equal('problem', '1')) + ->orNotFilter(Filter::equal('handled', '1')); + +$filter = $query->getFilter(); +``` + +## Events + +The `Events` trait wraps [Evenement](https://github.com/igorw/evenement) and +adds event validation. Declare event name constants on the class to give +callers a typo-safe API and to let `isValidEvent()` enforce an explicit +allow-list: + +```php +use ipl\Stdlib\Events; + +class Connection +{ + use Events; + + public const ON_CONNECT = 'connected'; + public const ON_DISCONNECT = 'disconnected'; + + protected function isValidEvent($event): bool + { + return in_array($event, [static::ON_CONNECT, static::ON_DISCONNECT], true); + } + + public function open(): void + { + // ... connect ... + $this->emit(self::ON_CONNECT, [$this]); + } + + public function close(): void + { + // ... disconnect ... + $this->emit(self::ON_DISCONNECT, [$this]); + } +} + +$conn = new Connection(); +$conn->on(Connection::ON_CONNECT, function (Connection $c): void { + echo "Connected\n"; +}); +$conn->on(Connection::ON_DISCONNECT, function (Connection $c): void { + echo "Disconnected\n"; +}); + +$conn->open(); +$conn->close(); +``` + +## Utility Helpers + +### Str + +`Str` offers string utilities that complement PHP's built-in functions. +It converts between naming conventions, splits and trims in one step, and +provides `startsWith` with case-insensitive matching. + +```php +use ipl\Stdlib\Str; + +// Convert snake_case or kebab-case identifiers to camelCase: +Str::camel('host_name'); // 'hostName' +Str::camel('display-name'); // 'displayName' + +// Split on a delimiter and trim whitespace from every part in one pass: +Str::trimSplit(' foo , bar , baz '); // ['foo', 'bar', 'baz'] +Str::trimSplit('root:secret', ':'); // ['root', 'secret'] + +// Always return exactly $limit parts: pads with null if the delimiter is +// absent, and fold any remainder into the last part if there are more +// separators than expected: +[$user, $pass] = Str::symmetricSplit('root', ':', 2); // ['root', null] +[$user, $pass] = Str::symmetricSplit('root:secret:extra', ':', 2); // ['root', 'secret:extra'] + +// Case-insensitive prefix check: +Str::startsWith('Foobar', 'foo', caseSensitive: false); // true +Str::startsWith('foobar', 'foo'); // true +``` + +### Seq + +`Seq` searches arrays, iterators, and generators by value, key, or callback +without first materializing them into arrays. When the second argument to +`find` or `contains` is a non-callable, it is compared by value; pass a +closure to match by predicate instead: + +```php +use ipl\Stdlib\Seq; + +$users = [ + 'alice' => 'admin', + 'bob' => 'viewer', +]; + +Seq::contains($users, 'viewer'); // true + +[$key, $value] = Seq::find($users, 'admin'); // ['alice', 'admin'] + +// Match by predicate — returns as soon as a result is found: +[$key, $value] = Seq::find($users, fn(string $role): bool => $role !== 'admin'); // ['bob', 'viewer'] +``` + +### Iterable Helpers + +```php +use function ipl\Stdlib\iterable_key_first; +use function ipl\Stdlib\iterable_value_first; + +$map = [ + 'id' => 42, + 'name' => 'Alice', +]; + +iterable_key_first($map); // 'id' +iterable_value_first($map); // 42 + +// Works with generators and iterators — does not require an array: +iterable_key_first(new ArrayIterator(['a' => 1])); // 'a' +iterable_key_first([]); // null +``` + +### Grouping With `yield_groups` + +`yield_groups` partitions a pre-sorted traversable into named groups. +The callback must return at least the grouping criterion, but it can +also return a custom value and key. The traversable **must** be sorted +by the grouping criterion before being passed in; results are undefined +otherwise: + +```php +use function ipl\Stdlib\yield_groups; + +foreach (yield_groups($rows, fn(object $row): string => $row->category) as $category => $items) { + // $items contains all rows for $category. +} +``` + +### Other Utility Classes + +- `Data` — mutable key/value store +- `Messages` — collects user-facing messages and supports `sprintf`-style + placeholders +- `PriorityQueue` — extends `SplPriorityQueue` with stable insertion-order for + items at equal priority; iterate non-destructively with `yieldAll()` + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md) for a list of notable changes. + +## License + +`ipl/stdlib` is licensed under the terms of the [MIT License](LICENSE.md). From bc40e308cff29cde223067a4c86392e5fe935eff Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 23 Mar 2026 10:22:57 +0100 Subject: [PATCH 08/11] Normalize and complete PHPDoc across all source files --- src/BaseFilter.php | 4 +++- src/Contract/Filterable.php | 3 +++ src/Contract/Paginatable.php | 3 +++ src/Contract/PaginationInterface.php | 6 +++++- src/Contract/PluginLoader.php | 5 ++--- src/Contract/Translator.php | 2 +- src/Contract/Validator.php | 7 +++++-- src/Contract/ValidatorInterface.php | 6 +++++- src/Data.php | 3 +++ src/EventEmitter.php | 6 +++++- src/Events.php | 26 +++++++++++++++++++++++++- src/Filter.php | 13 +++++++++---- src/Filter/All.php | 3 +++ src/Filter/Any.php | 3 +++ src/Filter/Chain.php | 22 ++++++++++++---------- src/Filter/Condition.php | 4 +++- src/Filter/Equal.php | 12 +++++++----- src/Filter/GreaterThan.php | 3 +++ src/Filter/GreaterThanOrEqual.php | 3 +++ src/Filter/LessThan.php | 3 +++ src/Filter/LessThanOrEqual.php | 3 +++ src/Filter/Like.php | 12 +++++++----- src/Filter/MetaData.php | 4 +++- src/Filter/MetaDataProvider.php | 8 +++++++- src/Filter/None.php | 3 +++ src/Filter/Rule.php | 3 +++ src/Filter/Unequal.php | 12 +++++++----- src/Filter/Unlike.php | 12 +++++++----- src/Filters.php | 4 +++- src/MessageContainer.php | 6 +++++- src/Messages.php | 9 ++++++--- src/Plugins.php | 17 +++++++++++++++-- src/PriorityQueue.php | 8 +++----- src/Properties.php | 19 ++++++++++++++----- src/Seq.php | 11 +++++------ src/functions.php | 10 +++++----- 36 files changed, 202 insertions(+), 76 deletions(-) diff --git a/src/BaseFilter.php b/src/BaseFilter.php index 0b0a946..fb445db 100644 --- a/src/BaseFilter.php +++ b/src/BaseFilter.php @@ -4,9 +4,11 @@ use ipl\Stdlib\Filter\Rule; +/** + * Store and expose a base filter rule + */ trait BaseFilter { - /** @var ?Rule Base filter */ private ?Rule $baseFilter = null; /** diff --git a/src/Contract/Filterable.php b/src/Contract/Filterable.php index a6fcdb3..3fbaeaa 100644 --- a/src/Contract/Filterable.php +++ b/src/Contract/Filterable.php @@ -4,6 +4,9 @@ use ipl\Stdlib\Filter; +/** + * Manage filters on a query or collection + */ interface Filterable { /** diff --git a/src/Contract/Paginatable.php b/src/Contract/Paginatable.php index 1a63005..820afc6 100644 --- a/src/Contract/Paginatable.php +++ b/src/Contract/Paginatable.php @@ -4,6 +4,9 @@ use Countable; +/** + * Support limit and offset for paginated result sets + */ interface Paginatable extends Countable { /** diff --git a/src/Contract/PaginationInterface.php b/src/Contract/PaginationInterface.php index b00fa66..11c26c7 100644 --- a/src/Contract/PaginationInterface.php +++ b/src/Contract/PaginationInterface.php @@ -2,7 +2,11 @@ namespace ipl\Stdlib\Contract; -/** @deprecated Use {@link Paginatable} instead */ +/** + * Deprecated predecessor of {@see Paginatable} + * + * @deprecated Use {@see Paginatable} instead + */ interface PaginationInterface extends Paginatable { } diff --git a/src/Contract/PluginLoader.php b/src/Contract/PluginLoader.php index d081a47..9425542 100644 --- a/src/Contract/PluginLoader.php +++ b/src/Contract/PluginLoader.php @@ -3,10 +3,9 @@ namespace ipl\Stdlib\Contract; /** - * Representation of plugin loaders + * Load plugin class names by plugin name * - * Plugin loaders must implement the {@link load()} method in order to provide the fully qualified class name of a - * plugin to load. + * Implementations must provide the fully qualified class name of a plugin via {@see load()}. */ interface PluginLoader { diff --git a/src/Contract/Translator.php b/src/Contract/Translator.php index 413d37e..bf0b6be 100644 --- a/src/Contract/Translator.php +++ b/src/Contract/Translator.php @@ -3,7 +3,7 @@ namespace ipl\Stdlib\Contract; /** - * Representation of translators + * Translate messages with optional context, domain scope, and plural forms */ interface Translator { diff --git a/src/Contract/Validator.php b/src/Contract/Validator.php index e43821d..9f3a683 100644 --- a/src/Contract/Validator.php +++ b/src/Contract/Validator.php @@ -2,10 +2,13 @@ namespace ipl\Stdlib\Contract; +/** + * Validate values and collect error messages + */ interface Validator { /** - * Get whether the given value is valid + * Check whether the given value is valid * * @param mixed $value * @@ -16,7 +19,7 @@ public function isValid($value); /** * Get the validation error messages * - * @return array + * @return string[] */ public function getMessages(); } diff --git a/src/Contract/ValidatorInterface.php b/src/Contract/ValidatorInterface.php index 36cf55e..c116857 100644 --- a/src/Contract/ValidatorInterface.php +++ b/src/Contract/ValidatorInterface.php @@ -2,7 +2,11 @@ namespace ipl\Stdlib\Contract; -/** @deprecated Use {@link Validator} instead */ +/** + * Deprecated predecessor of {@see Validator} + * + * @deprecated Use {@see Validator} instead + */ interface ValidatorInterface extends Validator { } diff --git a/src/Data.php b/src/Data.php index d54d598..60c212d 100644 --- a/src/Data.php +++ b/src/Data.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib; +/** + * Store and retrieve arbitrary key-value data + */ class Data { /** @var array */ diff --git a/src/EventEmitter.php b/src/EventEmitter.php index 6d189ee..264ae82 100644 --- a/src/EventEmitter.php +++ b/src/EventEmitter.php @@ -2,7 +2,11 @@ namespace ipl\Stdlib; -/** @deprecated Use {@link Events} instead */ +/** + * Deprecated predecessor of {@see Events} + * + * @deprecated Use {@see Events} instead + */ trait EventEmitter { use Events; diff --git a/src/Events.php b/src/Events.php index 3ecae41..2ef2357 100644 --- a/src/Events.php +++ b/src/Events.php @@ -5,18 +5,25 @@ use Evenement\EventEmitterTrait; use InvalidArgumentException; +/** + * Register listeners and emit named events with optional event-name validation + */ trait Events { use EventEmitterTrait { EventEmitterTrait::on as private evenementUnvalidatedOn; } - /** @var array */ + /** @var array */ protected array $eventsEmittedOnce = []; /** + * Emit the given event at most once, ignoring subsequent calls + * * @param string $event * @param array $arguments + * + * @return void */ protected function emitOnce(string $event, array $arguments = []): void { @@ -27,9 +34,14 @@ protected function emitOnce(string $event, array $arguments = []): void } /** + * Register a listener for the given event, validating the event name first + * * @param string $event * @param callable $listener + * * @return $this + * + * @throws InvalidArgumentException If the event name is not valid */ public function on($event, callable $listener): static { @@ -39,6 +51,15 @@ public function on($event, callable $listener): static return $this; } + /** + * Assert that the given event name is valid + * + * @param string $event + * + * @return void + * + * @throws InvalidArgumentException If the event name is not valid + */ protected function assertValidEvent(string $event): void { if (! $this->isValidEvent($event)) { @@ -47,7 +68,10 @@ protected function assertValidEvent(string $event): void } /** + * Check whether the given event name is valid + * * @param string $event + * * @return bool */ public function isValidEvent($event) diff --git a/src/Filter.php b/src/Filter.php index 8450a98..4ddcb74 100644 --- a/src/Filter.php +++ b/src/Filter.php @@ -17,20 +17,25 @@ use ipl\Stdlib\Filter\Unequal; use ipl\Stdlib\Filter\Unlike; +/** + * Build filter rules and evaluate them against rows + */ class Filter { /** - * protected - This is only a factory class + * Create a new Filter + * + * Intentionally protected; use the static factory methods instead. */ protected function __construct() { } /** - * Return whether the given rule matches the given item + * Check whether the given rule matches the given item * * @param Rule $rule - * @param object|array $row + * @param array|object $row * * @return bool */ @@ -500,7 +505,7 @@ protected function extractValue(string $column, object $row): mixed * Normalize type of $value to the one of $rowValue * * For details on how this works please see the corresponding test - * {@see \ipl\Tests\Stdlib\FilterTest::testConditionsAreValueTypeAgnostic} + * {@see \ipl\Tests\Stdlib\FilterTest::testConditionsAreValueTypeAgnostic}. * * @param mixed $rowValue * @param mixed $value diff --git a/src/Filter/All.php b/src/Filter/All.php index 67b47b6..b89f3ab 100644 --- a/src/Filter/All.php +++ b/src/Filter/All.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Filter chain that matches when all contained rules match + */ class All extends Chain { } diff --git a/src/Filter/Any.php b/src/Filter/Any.php index 5d47ebe..31db673 100644 --- a/src/Filter/Any.php +++ b/src/Filter/Any.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Filter chain that matches when at least one contained rule matches + */ class Any extends Chain { } diff --git a/src/Filter/Chain.php b/src/Filter/Chain.php index 0c75d53..cf18a60 100644 --- a/src/Filter/Chain.php +++ b/src/Filter/Chain.php @@ -8,7 +8,11 @@ use OutOfBoundsException; use Traversable; -/** @implements IteratorAggregate */ +/** + * Abstract filter chain that holds an ordered list of rules + * + * @implements IteratorAggregate + */ abstract class Chain implements Rule, MetaDataProvider, IteratorAggregate, Countable { use MetaData; @@ -43,7 +47,7 @@ public function __clone() } /** - * Get an iterator this chain's rules + * Get an iterator for this chain's rules * * @return ArrayIterator */ @@ -72,8 +76,9 @@ public function add(Rule $rule): static * @param Rule $rule * @param Rule $before * - * @throws OutOfBoundsException In case no existing rule is found * @return $this + * + * @throws OutOfBoundsException If the reference rule is not found */ public function insertBefore(Rule $rule, Rule $before): static { @@ -93,8 +98,9 @@ public function insertBefore(Rule $rule, Rule $before): static * @param Rule $rule * @param Rule $after * - * @throws OutOfBoundsException In case no existing rule is found * @return $this + * + * @throws OutOfBoundsException If the reference rule is not found */ public function insertAfter(Rule $rule, Rule $after): static { @@ -126,8 +132,9 @@ public function has(Rule $rule): bool * @param Rule $rule * @param Rule $replacement * - * @throws OutOfBoundsException In case no existing rule is found * @return $this + * + * @throws OutOfBoundsException If the rule to replace is not found */ public function replace(Rule $rule, Rule $replacement): static { @@ -168,11 +175,6 @@ public function isEmpty(): bool return empty($this->rules); } - /** - * Count this chain's rules - * - * @return int - */ public function count(): int { return count($this->rules); diff --git a/src/Filter/Condition.php b/src/Filter/Condition.php index dfb160a..6f47f80 100644 --- a/src/Filter/Condition.php +++ b/src/Filter/Condition.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Abstract filter condition matching a column against a value + */ abstract class Condition implements Rule, MetaDataProvider { use MetaData; @@ -9,7 +12,6 @@ abstract class Condition implements Rule, MetaDataProvider /** @var string|string[] */ protected string|array $column = []; - /** @var mixed */ protected mixed $value = null; /** diff --git a/src/Filter/Equal.php b/src/Filter/Equal.php index f5f22de..e5f3e07 100644 --- a/src/Filter/Equal.php +++ b/src/Filter/Equal.php @@ -2,9 +2,11 @@ namespace ipl\Stdlib\Filter; +/** + * Match when the column value equals the filter value + */ class Equal extends Condition { - /** @var bool */ protected bool $ignoreCase = false; /** @@ -20,10 +22,10 @@ public function ignoreCase(): static } /** - * Return whether this rule ignores case - * - * @return bool - */ + * Return whether this rule ignores case + * + * @return bool + */ public function ignoresCase(): bool { return $this->ignoreCase; diff --git a/src/Filter/GreaterThan.php b/src/Filter/GreaterThan.php index fd8190c..c9f332f 100644 --- a/src/Filter/GreaterThan.php +++ b/src/Filter/GreaterThan.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Match when the column value is strictly greater than the filter value + */ class GreaterThan extends Condition { } diff --git a/src/Filter/GreaterThanOrEqual.php b/src/Filter/GreaterThanOrEqual.php index 4cd4a73..479db91 100644 --- a/src/Filter/GreaterThanOrEqual.php +++ b/src/Filter/GreaterThanOrEqual.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Match when the column value is greater than or equal to the filter value + */ class GreaterThanOrEqual extends Condition { } diff --git a/src/Filter/LessThan.php b/src/Filter/LessThan.php index 297493f..4a2746a 100644 --- a/src/Filter/LessThan.php +++ b/src/Filter/LessThan.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Match when the column value is strictly less than the filter value + */ class LessThan extends Condition { } diff --git a/src/Filter/LessThanOrEqual.php b/src/Filter/LessThanOrEqual.php index ef35974..cd8ae41 100644 --- a/src/Filter/LessThanOrEqual.php +++ b/src/Filter/LessThanOrEqual.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Match when the column value is less than or equal to the filter value + */ class LessThanOrEqual extends Condition { } diff --git a/src/Filter/Like.php b/src/Filter/Like.php index d720c71..0a40348 100644 --- a/src/Filter/Like.php +++ b/src/Filter/Like.php @@ -2,9 +2,11 @@ namespace ipl\Stdlib\Filter; +/** + * Match when the column value is similar to the filter value, supporting `*` wildcards + */ class Like extends Condition { - /** @var bool */ protected bool $ignoreCase = false; /** @@ -20,10 +22,10 @@ public function ignoreCase(): static } /** - * Return whether this rule ignores case - * - * @return bool - */ + * Return whether this rule ignores case + * + * @return bool + */ public function ignoresCase(): bool { return $this->ignoreCase; diff --git a/src/Filter/MetaData.php b/src/Filter/MetaData.php index f07fdaf..c0e2a29 100644 --- a/src/Filter/MetaData.php +++ b/src/Filter/MetaData.php @@ -4,9 +4,11 @@ use ipl\Stdlib\Data; +/** + * Complement {@see MetaDataProvider} by lazily creating a {@see Data} bag on first access + */ trait MetaData { - /** @var ?Data */ protected ?Data $metaData = null; public function metaData(): Data diff --git a/src/Filter/MetaDataProvider.php b/src/Filter/MetaDataProvider.php index c4f0ab9..df0feb4 100644 --- a/src/Filter/MetaDataProvider.php +++ b/src/Filter/MetaDataProvider.php @@ -4,10 +4,16 @@ use ipl\Stdlib\Data; +/** + * Provide access to arbitrary rule meta data + */ interface MetaDataProvider { /** - * Get this rule's meta data + * Get this rule's metadata + * + * Implementations must return the same bag on every call, creating it on + * first access or upfront. * * @return Data */ diff --git a/src/Filter/None.php b/src/Filter/None.php index a1b14f7..d4eee7a 100644 --- a/src/Filter/None.php +++ b/src/Filter/None.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Filter chain that matches when none of the contained rules match + */ class None extends Chain { } diff --git a/src/Filter/Rule.php b/src/Filter/Rule.php index dc83c80..7eb210d 100644 --- a/src/Filter/Rule.php +++ b/src/Filter/Rule.php @@ -2,6 +2,9 @@ namespace ipl\Stdlib\Filter; +/** + * Marker interface for filter rules (chains and conditions) + */ interface Rule { } diff --git a/src/Filter/Unequal.php b/src/Filter/Unequal.php index 7742dfe..bf90fd2 100644 --- a/src/Filter/Unequal.php +++ b/src/Filter/Unequal.php @@ -2,9 +2,11 @@ namespace ipl\Stdlib\Filter; +/** + * Match when the column value does not equal the filter value + */ class Unequal extends Condition { - /** @var bool */ protected bool $ignoreCase = false; /** @@ -20,10 +22,10 @@ public function ignoreCase(): static } /** - * Return whether this rule ignores case - * - * @return bool - */ + * Return whether this rule ignores case + * + * @return bool + */ public function ignoresCase(): bool { return $this->ignoreCase; diff --git a/src/Filter/Unlike.php b/src/Filter/Unlike.php index df8936d..b99c223 100644 --- a/src/Filter/Unlike.php +++ b/src/Filter/Unlike.php @@ -2,9 +2,11 @@ namespace ipl\Stdlib\Filter; +/** + * Match when the column value does not match the filter value, supporting `*` wildcards + */ class Unlike extends Condition { - /** @var bool */ protected bool $ignoreCase = false; /** @@ -20,10 +22,10 @@ public function ignoreCase(): static } /** - * Return whether this rule ignores case - * - * @return bool - */ + * Return whether this rule ignores case + * + * @return bool + */ public function ignoresCase(): bool { return $this->ignoreCase; diff --git a/src/Filters.php b/src/Filters.php index c4b03ad..acd1020 100644 --- a/src/Filters.php +++ b/src/Filters.php @@ -2,9 +2,11 @@ namespace ipl\Stdlib; +/** + * Add and compose filter rules on a query or collection + */ trait Filters { - /** @var ?Filter\Chain */ protected ?Filter\Chain $filter = null; public function getFilter(): Filter\Chain diff --git a/src/MessageContainer.php b/src/MessageContainer.php index 3b383b1..76822ee 100644 --- a/src/MessageContainer.php +++ b/src/MessageContainer.php @@ -2,7 +2,11 @@ namespace ipl\Stdlib; -/** @deprecated Use {@link Messages} instead */ +/** + * Deprecated predecessor of {@see Messages} + * + * @deprecated Use {@see Messages} instead + */ trait MessageContainer { use Messages; diff --git a/src/Messages.php b/src/Messages.php index a728c0f..4b7086d 100644 --- a/src/Messages.php +++ b/src/Messages.php @@ -2,9 +2,12 @@ namespace ipl\Stdlib; +/** + * Store, retrieve, and manage a list of string messages + */ trait Messages { - /** @var array */ + /** @var string[] */ protected array $messages = []; /** @@ -20,7 +23,7 @@ public function hasMessages(): bool /** * Get all messages * - * @return array + * @return string[] */ public function getMessages() { @@ -67,7 +70,7 @@ public function addMessage(string $message, mixed ...$args): static /** * Add the given messages * - * @param array $messages + * @param string[] $messages * * @return $this */ diff --git a/src/Plugins.php b/src/Plugins.php index e70dcb4..d5f59b0 100644 --- a/src/Plugins.php +++ b/src/Plugins.php @@ -5,9 +5,12 @@ use ipl\Stdlib\Contract\PluginLoader; use ipl\Stdlib\Loader\AutoloadingPluginLoader; +/** + * Register plugin loaders by type and resolve plugin class names + */ trait Plugins { - /** @var array Registered plugin loaders by type */ + /** @var array Registered plugin loaders by type */ protected array $pluginLoaders = []; /** @@ -75,7 +78,6 @@ public function addPluginLoader( public function loadPlugin(string $type, string $name): string|false { if ($this->hasPluginLoader($type)) { - /** @var PluginLoader $loader */ foreach ($this->pluginLoaders[$type] as $loader) { $class = $loader->load($name); if ($class) { @@ -87,6 +89,17 @@ public function loadPlugin(string $type, string $name): string|false return false; } + /** + * Add a default plugin loader for the given type + * + * Default loaders are appended after any loaders added via {@see addPluginLoader()}. + * + * @param string $type + * @param PluginLoader|string $loaderOrNamespace + * @param string $postfix + * + * @return $this + */ protected function addDefaultPluginLoader( string $type, PluginLoader|string $loaderOrNamespace, diff --git a/src/PriorityQueue.php b/src/PriorityQueue.php index 1c10736..a2795b2 100644 --- a/src/PriorityQueue.php +++ b/src/PriorityQueue.php @@ -14,13 +14,11 @@ */ class PriorityQueue extends SplPriorityQueue { - /** @var int */ + /** @var int Decreasing insertion counter for stable ordering at equal priorities */ protected int $serial = PHP_INT_MAX; /** - * Inserts an element in the queue by sifting it up. - * - * Maintains insertion order for items with the same priority. + * Insert an element in the queue, maintaining insertion order for equal priorities * * @param TValue $value * @param TPriority $priority @@ -35,7 +33,7 @@ public function insert(mixed $value, mixed $priority): true /** * Yield all items as priority-value pairs * - * @return Generator + * @return Generator */ public function yieldAll(): Generator { diff --git a/src/Properties.php b/src/Properties.php index 015f0d3..01d659b 100644 --- a/src/Properties.php +++ b/src/Properties.php @@ -2,15 +2,16 @@ namespace ipl\Stdlib; +use Generator; use OutOfBoundsException; use Traversable; /** - * Trait for property access, mutation and array access. + * Trait for property access, mutation, and array access */ trait Properties { - /** @var array */ + /** @var array */ private array $properties = []; /** @@ -38,7 +39,7 @@ public function hasProperty(string $key): bool /** * Set the given properties * - * @param array $properties + * @param array $properties * * @return $this */ @@ -87,7 +88,7 @@ protected function setProperty(string $key, mixed $value): static /** * Iterate over all existing properties * - * @return Traversable + * @return Generator */ public function getIterator(): Traversable { @@ -125,6 +126,8 @@ public function offsetGet(mixed $offset): mixed * * @param mixed $offset * @param mixed $value + * + * @return void */ public function offsetSet(mixed $offset, mixed $value): void { @@ -135,6 +138,8 @@ public function offsetSet(mixed $offset, mixed $value): void * Unset the value for an offset * * @param mixed $offset + * + * @return void */ public function offsetUnset(mixed $offset): void { @@ -148,7 +153,7 @@ public function offsetUnset(mixed $offset): void * e.g. `$value = $object->property;`. * Do not call this method directly. * - * @param mixed $key + * @param string $key * * @return mixed */ @@ -166,6 +171,8 @@ public function __get(string $key): mixed * * @param string $key * @param mixed $value + * + * @return void */ public function __set(string $key, mixed $value): void { @@ -196,6 +203,8 @@ public function __isset(string $key): bool * Do not call this method directly. * * @param string $key + * + * @return void */ public function __unset(string $key): void { diff --git a/src/Seq.php b/src/Seq.php index acc303f..349e870 100644 --- a/src/Seq.php +++ b/src/Seq.php @@ -12,7 +12,7 @@ class Seq /** * Check if the traversable contains the given needle * - * @param iterable $traversable + * @param iterable $traversable * @param mixed $needle Might also be a closure * @param bool $caseSensitive Whether strings should be compared case-sensitive * @@ -26,12 +26,11 @@ public static function contains(iterable $traversable, mixed $needle, bool $case /** * Search in the traversable for the given needle and return its key and value * - * @param iterable $traversable + * @param iterable $traversable * @param mixed $needle Might also be a closure * @param bool $caseSensitive Whether strings should be compared case-sensitive * - * @return array An array with two entries, the first is the key, then the value. - * Both are null if nothing is found. + * @return array{0: mixed, 1: mixed} The found key and value, or [null, null] if nothing is found */ public static function find(iterable $traversable, mixed $needle, bool $caseSensitive = true): array { @@ -66,7 +65,7 @@ public static function find(iterable $traversable, mixed $needle, bool $caseSens /** * Search in the traversable for the given needle and return its key * - * @param iterable $traversable + * @param iterable $traversable * @param mixed $needle Might also be a closure * @param bool $caseSensitive Whether strings should be compared case-sensitive * @@ -80,7 +79,7 @@ public static function findKey(iterable $traversable, mixed $needle, bool $caseS /** * Search in the traversable for the given needle and return its value * - * @param iterable $traversable + * @param iterable $traversable * @param mixed $needle Might also be a closure * @param bool $caseSensitive Whether strings should be compared case-sensitive * diff --git a/src/functions.php b/src/functions.php index 5a829cb..e221470 100644 --- a/src/functions.php +++ b/src/functions.php @@ -27,7 +27,7 @@ function get_php_type(mixed $subject): string * * @param iterable|stdClass $subject * - * @return array + * @return array */ function arrayval(iterable|stdClass $subject): array { @@ -46,7 +46,7 @@ function arrayval(iterable|stdClass $subject): array /** * Get the first key of an iterable * - * @param iterable $iterable + * @param iterable $iterable * * @return int|string|null The first key of the iterable if it is not empty, null otherwise */ @@ -62,9 +62,9 @@ function iterable_key_first(iterable $iterable): int|string|null /** * Get the first value of an iterable * - * @param iterable $iterable + * @param iterable $iterable * - * @return ?mixed + * @return mixed|null The first value of the iterable if it is not empty, null otherwise */ function iterable_value_first(iterable $iterable): mixed { @@ -84,7 +84,7 @@ function iterable_value_first(iterable $iterable): mixed * @param Traversable $traversable * @param callable(mixed $value, mixed $key): array{0: mixed, 1?: mixed, 2?: mixed} $groupBy * - * @return Generator + * @return Generator */ function yield_groups(Traversable $traversable, callable $groupBy): Generator { From 07de45d8df63ac97defccc6e60d941fc7c88ad17 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 23 Mar 2026 11:31:46 +0100 Subject: [PATCH 09/11] Clean up inline comments and tighten wording Inline comments should read as complete sentences and be easy to scan. Ending each comment with a full stop signals that the thought is complete and keeps the style consistent across the codebase. Where comments were verbose or imprecise, take the opportunity to tighten the wording without changing meaning. --- src/Filter.php | 4 ++-- src/PriorityQueue.php | 2 +- src/functions.php | 2 +- src/functions_include.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Filter.php b/src/Filter.php index 4ddcb74..0e69f8f 100644 --- a/src/Filter.php +++ b/src/Filter.php @@ -239,7 +239,7 @@ protected function performEqualityMatch(mixed $value, mixed $rowValue, bool $ign /** @var string|string[] $value {@see self::normalizeTypes} ensures this is the case */ $value = is_array($value) ? array_map('strtolower', $value) - : ($value === null ? null : strtolower($value)); // phpstan is wrong here + : ($value === null ? null : strtolower($value)); // PHPStan incorrectly infers the type here. } if (is_array($value)) { @@ -265,7 +265,7 @@ protected function performSimilarityMatch(mixed $value, mixed $rowValue, bool $i /** @var string|string[] $value {@see self::normalizeTypes} ensures this is the case */ $value = is_array($value) ? array_map('strtolower', $value) - : ($value === null ? null : strtolower($value)); // phpstan is wrong here + : ($value === null ? null : strtolower($value)); // PHPStan incorrectly infers the type here. } if (is_array($value)) { diff --git a/src/PriorityQueue.php b/src/PriorityQueue.php index a2795b2..d423cb3 100644 --- a/src/PriorityQueue.php +++ b/src/PriorityQueue.php @@ -37,7 +37,7 @@ public function insert(mixed $value, mixed $priority): true */ public function yieldAll(): Generator { - // Clone queue because the SplPriorityQueue acts as a heap and thus items are removed upon iteration + // Clone the queue because SplPriorityQueue acts as a heap and removes items upon iteration. $queue = clone $this; $queue->setExtractFlags(static::EXTR_BOTH); diff --git a/src/functions.php b/src/functions.php index e221470..596e982 100644 --- a/src/functions.php +++ b/src/functions.php @@ -39,7 +39,7 @@ function arrayval(iterable|stdClass $subject): array return (array) $subject; } - // Works for generators too + // Also works for generators. return iterator_to_array($subject); } diff --git a/src/functions_include.php b/src/functions_include.php index 9a2dc6f..4eda869 100644 --- a/src/functions_include.php +++ b/src/functions_include.php @@ -1,6 +1,6 @@ Date: Tue, 24 Mar 2026 13:32:33 +0100 Subject: [PATCH 10/11] Optimize imports --- src/functions.php | 3 +-- tests/MessagesTest.php | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/functions.php b/src/functions.php index 596e982..adaa820 100644 --- a/src/functions.php +++ b/src/functions.php @@ -3,10 +3,9 @@ namespace ipl\Stdlib; use Generator; -use InvalidArgumentException; use IteratorIterator; -use Traversable; use stdClass; +use Traversable; /** * Detect and return the PHP type of the given subject diff --git a/tests/MessagesTest.php b/tests/MessagesTest.php index df681b1..0088a9e 100644 --- a/tests/MessagesTest.php +++ b/tests/MessagesTest.php @@ -3,7 +3,6 @@ namespace ipl\Tests\Stdlib; use ipl\Stdlib\Messages; -use stdClass; class MessagesTest extends TestCase { From 75355bff9b0b27d94854d5961eb8d7224ab322a0 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 24 Mar 2026 17:28:53 +0100 Subject: [PATCH 11/11] Document empty-argument behavior for `all`, `any`, and `none` All three methods accept zero rules, but the existing PHPDoc left callers to infer what the resulting rule would do. An empty `All` or `None` always matches; an empty `Any` never does. Co-authored-by: Dennis <47061464+dennisorlando@users.noreply.github.com> --- src/Filter.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Filter.php b/src/Filter.php index 0e69f8f..9287007 100644 --- a/src/Filter.php +++ b/src/Filter.php @@ -51,6 +51,8 @@ public static function match(Rule $rule, array|object $row): bool /** * Create a rule that matches if **all** of the given rules do * + * If no rules are given, the resulting rule always matches. + * * @param Rule ...$rules * * @return Chain @@ -66,7 +68,7 @@ public static function all(Rule ...$rules): Chain * @param All $rules * @param object $row * - * @return bool + * @return bool True if all rules match; always true if no rules are given */ protected function matchAll(All $rules, object $row): bool { @@ -82,6 +84,8 @@ protected function matchAll(All $rules, object $row): bool /** * Create a rule that matches if **any** of the given rules do * + * If no rules are given, the resulting rule never matches. + * * @param Rule ...$rules * * @return Chain @@ -97,7 +101,7 @@ public static function any(Rule ...$rules): Chain * @param Any $rules * @param object $row * - * @return bool + * @return bool True if any rule matches; always false if no rules are given */ protected function matchAny(Any $rules, object $row): bool { @@ -113,6 +117,8 @@ protected function matchAny(Any $rules, object $row): bool /** * Create a rule that matches if **none** of the given rules do * + * If no rules are given, the resulting rule always matches. + * * @param Rule ...$rules * * @return Chain @@ -128,7 +134,7 @@ public static function none(Rule ...$rules): Chain * @param None $rules * @param object $row * - * @return bool + * @return bool True if no rules match; always true if no rules are given */ protected function matchNone(None $rules, object $row): bool {