Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ on:
push:
branches:
- main
- support/*
pull_request:
branches:
- main
workflow_dispatch:

jobs:
php:
Expand Down
12 changes: 4 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
121 changes: 121 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions LICENSE → LICENSE.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
223 changes: 220 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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).
Loading
Loading