Skip to content

Commit 82d8a63

Browse files
authored
Maintenance and release housekeeping (#71)
This PR consolidates several maintenance tasks to bring the repository in line with shared conventions across our projects. CI: - Extend push builds to `support/*` and trigger validation for all pull requests regardless of the target branch. - Enable `workflow_dispatch` for ad‑hoc verification. Repository: - Add `.gitattributes` to exclude CI, Git configuration, and test‑related files from `git archive` outputs. - Enable Composer’s `sort‑packages` setting and normalize `composer.json`. - Convert the license file to `LICENSE.md` to align with the repository’s Markdown‑based documentation and establish a consistent convention across all repositories. Documentation: - Add `CHANGELOG.md` to summarize notable changes per release. - Expand `README.md` with a package overview, installation requirements, and focused examples for the filter system, Events trait, and utility functions. - Normalize and complete PHPDoc across all source files. - Clean up inline comments and tighten wording.
2 parents 5049aa2 + 75355bf commit 82d8a63

45 files changed

Lines changed: 584 additions & 109 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Exclude Git and CI configuration files from archives.
2+
.git* export-ignore
3+
4+
# Exclude test-related files from archives.
5+
tests/ export-ignore
6+
phpstan* export-ignore

.github/workflows/php.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ on:
44
push:
55
branches:
66
- main
7+
- support/*
78
pull_request:
8-
branches:
9-
- main
9+
workflow_dispatch:
1010

1111
jobs:
1212
php:

.gitignore

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
# Exclude all hidden files
2-
.*
3-
4-
# Except those related to Git (and GitHub)
5-
!.git*
6-
7-
# Exclude files from composer install
8-
vendor/
1+
# Ignore Composer installation artifacts; composer.lock is intentionally
2+
# excluded as this is a library - applications depending on this package
3+
# manage their own lock file.
4+
/vendor/
95
composer.lock

CHANGELOG.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Changelog
2+
3+
All notable changes to this library are documented in this file.
4+
5+
## [Unreleased]
6+
7+
- **Breaking** Raise minimum PHP version to 8.2 (#63)
8+
- Add strict type declarations (#63)
9+
- Support PHP 8.5 (#61)
10+
11+
## [v0.14.0] — 2024-07-15
12+
13+
- **Breaking** Restrict `Str::symmetricSplit()`'s `$default` parameter to `?string` (#55)
14+
- Support PHP 8.3
15+
16+
## [v0.13.0] — 2023-09-21
17+
18+
- **Breaking** `Seq` no longer treats function names as needles (#42)
19+
- Add `iterable_value_first()` (#49)
20+
- Add `yield_groups()` to group sorted iterators by callback-derived criteria (#46)
21+
- Fix `Str::symmetricSplit()` to return padded array when subject is `null` (#45)
22+
- Support PHP 8.2 (#39)
23+
24+
25+
## [v0.12.1] — 2022-12-13
26+
27+
- Refine `Str` method signatures and `null` handling to improve PHP 8.1 compatibility (#37)
28+
29+
## [v0.12.0] — 2022-06-15
30+
31+
- **Breaking** Raise the minimum supported PHP version to 7.2 (#29)
32+
- **Breaking** `Filter::equal()` and `Filter::unequal()` no longer perform wildcard matching;
33+
use `Filter::like()` and `Filter::unlike()` instead (#33)
34+
- **Breaking** Remove mutator and closure support from `Properties` (#31)
35+
- Add iteration support to `Properties` via `IteratorAggregate` (#32)
36+
- Support PHP 8.1 (#29)
37+
38+
## [v0.11.0] — 2022-03-23
39+
40+
- **Breaking** Drop PHP 5.6 support; require PHP ≥ 7.0 (#28)
41+
- Introduce `BaseFilter` trait for simple filter-aware classes (#28)
42+
43+
## [v0.10.0] — 2021-11-10
44+
45+
- Add the `Seq` utility for searching iterables by key or value (#26)
46+
- Fix `Properties` to not mutate values on set (#23); resolve closures before
47+
mutation (#24)
48+
- Support PHP 8 (#21)
49+
50+
## [v0.9.0] — 2021-03-19
51+
52+
- **Breaking** `Filter\Chain` and `Filter\Condition` no longer tolerate dynamic properties;
53+
use dedicated metadata instead (#20)
54+
- Introduce `Filterable` contract interface and `Filters` trait for
55+
filter-aware objects (#19)
56+
- Introduce `Data` class for arbitrary key-value metadata storage (#20)
57+
- Introduce `MetaDataProvider` interface for filter rule metadata (#20)
58+
- Extend `Filter\Chain` with `insertBefore()` and `insertAfter()` (#20)
59+
60+
## [v0.8.0] — 2021-01-14
61+
62+
- Introduce filter system: `Filter` facade and `Filter\Rule`, `Filter\Chain`
63+
(`All`, `Any`, `None`), and `Filter\Condition` (`Equal`, `Unequal`,
64+
`GreaterThan`, `GreaterThanOrEqual`, `LessThan`, `LessThanOrEqual`) (#15)
65+
- Add `random_bytes()` polyfill for PHP < 7.0 (#18)
66+
67+
## [v0.7.0] — 2020-10-19
68+
69+
- Introduce `Properties` trait for dynamic typed property storage (#16)
70+
71+
## [v0.6.0] — 2020-10-12
72+
73+
- Introduce `Translator` contract interface (#14)
74+
75+
## [v0.5.0] — 2020-03-12
76+
77+
- **Breaking** Raise the minimum supported PHP version to 5.6 (#12)
78+
- **Breaking** Refactor contracts and traits: rename `EventEmitter``Events`,
79+
`MessageContainer``Messages`; supersede `PaginationInterface` with
80+
`Paginatable`; supersede `ValidatorInterface` with `Validator` (#12)
81+
- Introduce new `PluginLoader` interface and `Plugins` trait (#12)
82+
- Introduce `PriorityQueue` class wrapping `SplPriorityQueue` with stable
83+
ordering (#13)
84+
85+
## [v0.4.0] — 2020-03-10
86+
87+
- Add `iterable_key_first()` for retrieving the first key from any iterable (#10)
88+
- Add `Str::symmetricSplit()`, `Str::trimSplit()`, and `Str::startsWith()`
89+
90+
## [v0.3.0] — 2019-10-16
91+
92+
- Introduce `PaginationInterface` (#5)
93+
- Introduce `Str` class with `camel()` case conversion
94+
- Add `is_iterable()` polyfill for PHP < 7.1
95+
96+
## [v0.2.0] — 2019-05-16
97+
98+
- Introduce `EventEmitter` trait, wrapping
99+
[Evenement](https://github.com/igorw/evenement) (#8)
100+
101+
## [v0.1.0] — 2019-03-26
102+
103+
Initial release providing `AutoloadingPluginLoader`, `MessageContainer` trait,
104+
`ValidatorInterface`, and type/iterable utility functions.
105+
106+
[Unreleased]: https://github.com/Icinga/ipl-stdlib/compare/v0.14.0...HEAD
107+
[v0.14.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.14.0
108+
[v0.13.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.13.0
109+
[v0.12.1]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.12.1
110+
[v0.12.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.12.0
111+
[v0.11.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.11.0
112+
[v0.10.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.10.0
113+
[v0.9.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.9.0
114+
[v0.8.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.8.0
115+
[v0.7.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.7.0
116+
[v0.6.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.6.0
117+
[v0.5.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.5.0
118+
[v0.4.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.4.0
119+
[v0.3.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.3.0
120+
[v0.2.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.2.0
121+
[v0.1.0]: https://github.com/Icinga/ipl-stdlib/releases/tag/v0.1.0

LICENSE renamed to LICENSE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
The MIT License
1+
# MIT License
22

3-
Copyright (c) 2018 Icinga GmbH https://www.icinga.com
3+
Copyright (c) 2018 Icinga GmbH https://icinga.com
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 220 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,222 @@
11
# Icinga PHP Library - Standard Library
22

3-
This is the Stdlib prototype for the Icinga PHP library ([ipl](https://github.com/Icinga/ipl)).
4-
Please do not use this for anything important yet, as all APIs, Interfaces and
5-
paths are still subject to change.
3+
`ipl/stdlib` provides reusable building blocks for Icinga PHP libraries and
4+
applications. It covers declarative filtering, event emission, string and
5+
iterable utilities, lightweight data and message containers, and a
6+
stable priority queue.
7+
8+
## Installation
9+
10+
The recommended way to install this library is via
11+
[Composer](https://getcomposer.org):
12+
13+
```shell
14+
composer require ipl/stdlib
15+
```
16+
17+
`ipl/stdlib` requires PHP 8.2 or later with the `openssl` extension.
18+
19+
## Usage
20+
21+
### Filter Rows With Declarative Rules
22+
23+
Build composable filter trees with `ipl\Stdlib\Filter` and evaluate them
24+
against arrays or objects:
25+
26+
```php
27+
use ipl\Stdlib\Filter;
28+
29+
$filter = Filter::all(
30+
Filter::equal('problem', '1'),
31+
Filter::none(Filter::equal('handled', '1')),
32+
Filter::like('service', 'www.*')
33+
);
34+
35+
$row = [
36+
'problem' => '1',
37+
'handled' => '0',
38+
'service' => 'www.icinga.com',
39+
];
40+
41+
if (Filter::match($filter, $row)) {
42+
// The row matches the rule set.
43+
}
44+
```
45+
46+
Available condition factories: `equal`, `unequal`, `like`, `unlike`,
47+
`greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`.
48+
Available logical factories: `all`, `any`, `none`, `not`.
49+
50+
### Build Filters Incrementally
51+
52+
When an object needs to collect filter conditions over time, use the `Filters`
53+
trait. It complements the `Filterable` contract and exposes `filter()`,
54+
`orFilter()`, `notFilter()`, and `orNotFilter()`:
55+
56+
```php
57+
use ipl\Stdlib\Contract\Filterable;
58+
use ipl\Stdlib\Filter;
59+
use ipl\Stdlib\Filters;
60+
61+
class Query implements Filterable
62+
{
63+
use Filters;
64+
}
65+
66+
$query = (new Query())
67+
->filter(Filter::equal('problem', '1'))
68+
->orNotFilter(Filter::equal('handled', '1'));
69+
70+
$filter = $query->getFilter();
71+
```
72+
73+
## Events
74+
75+
The `Events` trait wraps [Evenement](https://github.com/igorw/evenement) and
76+
adds event validation. Declare event name constants on the class to give
77+
callers a typo-safe API and to let `isValidEvent()` enforce an explicit
78+
allow-list:
79+
80+
```php
81+
use ipl\Stdlib\Events;
82+
83+
class Connection
84+
{
85+
use Events;
86+
87+
public const ON_CONNECT = 'connected';
88+
public const ON_DISCONNECT = 'disconnected';
89+
90+
protected function isValidEvent($event): bool
91+
{
92+
return in_array($event, [static::ON_CONNECT, static::ON_DISCONNECT], true);
93+
}
94+
95+
public function open(): void
96+
{
97+
// ... connect ...
98+
$this->emit(self::ON_CONNECT, [$this]);
99+
}
100+
101+
public function close(): void
102+
{
103+
// ... disconnect ...
104+
$this->emit(self::ON_DISCONNECT, [$this]);
105+
}
106+
}
107+
108+
$conn = new Connection();
109+
$conn->on(Connection::ON_CONNECT, function (Connection $c): void {
110+
echo "Connected\n";
111+
});
112+
$conn->on(Connection::ON_DISCONNECT, function (Connection $c): void {
113+
echo "Disconnected\n";
114+
});
115+
116+
$conn->open();
117+
$conn->close();
118+
```
119+
120+
## Utility Helpers
121+
122+
### Str
123+
124+
`Str` offers string utilities that complement PHP's built-in functions.
125+
It converts between naming conventions, splits and trims in one step, and
126+
provides `startsWith` with case-insensitive matching.
127+
128+
```php
129+
use ipl\Stdlib\Str;
130+
131+
// Convert snake_case or kebab-case identifiers to camelCase:
132+
Str::camel('host_name'); // 'hostName'
133+
Str::camel('display-name'); // 'displayName'
134+
135+
// Split on a delimiter and trim whitespace from every part in one pass:
136+
Str::trimSplit(' foo , bar , baz '); // ['foo', 'bar', 'baz']
137+
Str::trimSplit('root:secret', ':'); // ['root', 'secret']
138+
139+
// Always return exactly $limit parts: pads with null if the delimiter is
140+
// absent, and fold any remainder into the last part if there are more
141+
// separators than expected:
142+
[$user, $pass] = Str::symmetricSplit('root', ':', 2); // ['root', null]
143+
[$user, $pass] = Str::symmetricSplit('root:secret:extra', ':', 2); // ['root', 'secret:extra']
144+
145+
// Case-insensitive prefix check:
146+
Str::startsWith('Foobar', 'foo', caseSensitive: false); // true
147+
Str::startsWith('foobar', 'foo'); // true
148+
```
149+
150+
### Seq
151+
152+
`Seq` searches arrays, iterators, and generators by value, key, or callback
153+
without first materializing them into arrays. When the second argument to
154+
`find` or `contains` is a non-callable, it is compared by value; pass a
155+
closure to match by predicate instead:
156+
157+
```php
158+
use ipl\Stdlib\Seq;
159+
160+
$users = [
161+
'alice' => 'admin',
162+
'bob' => 'viewer',
163+
];
164+
165+
Seq::contains($users, 'viewer'); // true
166+
167+
[$key, $value] = Seq::find($users, 'admin'); // ['alice', 'admin']
168+
169+
// Match by predicate — returns as soon as a result is found:
170+
[$key, $value] = Seq::find($users, fn(string $role): bool => $role !== 'admin'); // ['bob', 'viewer']
171+
```
172+
173+
### Iterable Helpers
174+
175+
```php
176+
use function ipl\Stdlib\iterable_key_first;
177+
use function ipl\Stdlib\iterable_value_first;
178+
179+
$map = [
180+
'id' => 42,
181+
'name' => 'Alice',
182+
];
183+
184+
iterable_key_first($map); // 'id'
185+
iterable_value_first($map); // 42
186+
187+
// Works with generators and iterators — does not require an array:
188+
iterable_key_first(new ArrayIterator(['a' => 1])); // 'a'
189+
iterable_key_first([]); // null
190+
```
191+
192+
### Grouping With `yield_groups`
193+
194+
`yield_groups` partitions a pre-sorted traversable into named groups.
195+
The callback must return at least the grouping criterion, but it can
196+
also return a custom value and key. The traversable **must** be sorted
197+
by the grouping criterion before being passed in; results are undefined
198+
otherwise:
199+
200+
```php
201+
use function ipl\Stdlib\yield_groups;
202+
203+
foreach (yield_groups($rows, fn(object $row): string => $row->category) as $category => $items) {
204+
// $items contains all rows for $category.
205+
}
206+
```
207+
208+
### Other Utility Classes
209+
210+
- `Data` — mutable key/value store
211+
- `Messages` — collects user-facing messages and supports `sprintf`-style
212+
placeholders
213+
- `PriorityQueue` — extends `SplPriorityQueue` with stable insertion-order for
214+
items at equal priority; iterate non-destructively with `yieldAll()`
215+
216+
## Changelog
217+
218+
See [CHANGELOG.md](CHANGELOG.md) for a list of notable changes.
219+
220+
## License
221+
222+
`ipl/stdlib` is licensed under the terms of the [MIT License](LICENSE.md).

0 commit comments

Comments
 (0)