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
146 changes: 146 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: CI

on:
push:
branches: [main, "2.x", "feat/*", "fix/*"]
pull_request:
branches: [main, "2.x"]

jobs:
tests:
name: Tests Β· PHP ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ["7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: composer:v2
ini-values: error_reporting=E_ALL, display_errors=On

- name: Validate composer.json
run: composer validate --strict

- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: Cache Composer packages
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php${{ matrix.php }}-composer-

- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-interaction

- name: Run PHPUnit
run: vendor/bin/phpunit --testdox

static-analysis:
name: Static analysis Β· PHPStan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
coverage: none
tools: composer:v2

- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: Cache Composer packages
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.3-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php8.3-composer-

- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-interaction

- name: PHPStan (level 8)
run: vendor/bin/phpstan analyse --no-progress

code-style:
name: Code style Β· PHP-CS-Fixer
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
coverage: none
tools: composer:v2

- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: Cache Composer packages
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.3-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php8.3-composer-

- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-interaction

- name: PHP-CS-Fixer (dry-run + diff)
run: vendor/bin/php-cs-fixer fix --dry-run --diff

lowest-php-syntax:
name: Syntax Β· PHP ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ["5.6", "7.0", "7.1", "7.2"]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
# No Composer install on these PHP versions: PHPUnit 9.6
# requires PHP >= 7.3, but composer.json keeps a "php: >=5.6"
# contract for library consumers. We only verify that the
# source files themselves parse cleanly on these interpreters.

- name: Lint src/
run: find src -type f -name '*.php' -print0 | xargs -0 -n1 php -l

- name: Autoload smoke test
# Standalone script (not `php -r`) β€” on PHP 5.6 / 7.0 / 7.1 / 7.2
# `__DIR__` is not defined inside `php -r '...'`, which made the
# autoloader resolve `/src/...` and load nothing. As a file
# `__DIR__` reliably points at tests/compat/, so the relative
# path to src/ works on every PHP version we support.
run: php tests/compat/autoload-smoke.php
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
/.vs/
/.vscode/
/vendor/
/composer.lock
/composer.lock
/.phpunit.cache/
/.phpunit.result.cache
/.php-cs-fixer.cache
/build/
101 changes: 101 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

/**
* PHP-CS-Fixer configuration for initphp/events.
*
* Baseline: PSR-12. The handful of additions below codify the style
* the existing source already uses (short array syntax, ordered
* imports, single quotes, trailing commas in multi-line arrays).
*
* Important: rules that would inject modern type-system syntax
* (declare_strict_types, return type declarations, void return,
* nullable type declarations) are intentionally *off* because the
* runtime contract in composer.json is `php: >= 5.6` and those
* constructs would silently break that promise.
*/

$finder = PhpCsFixer\Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php')
->ignoreDotFiles(true)
->ignoreVCS(true);

return (new PhpCsFixer\Config())
->setRiskyAllowed(false)
->setUsingCache(true)
->setCacheFile(__DIR__ . '/.php-cs-fixer.cache')
->setFinder($finder)
->setRules([
'@PSR12' => true,

// PSR-12 makes constant visibility (`public const`) mandatory,
// but that syntax requires PHP 7.1+. We support PHP 5.6, so
// we restrict the modifier-keywords fixer (the new name for
// the deprecated `visibility_required`) to method/property
// only and leave bare `const` declarations alone.
'modifier_keywords' => ['elements' => ['method', 'property']],

// Keep empty closure bodies (`function () {}`) on one line β€”
// they read better than the 2-line equivalent. Non-empty
// single-line bodies still get expanded by PSR-12's
// statement_indentation rule, which is fine.
'single_line_empty_body' => true,
'braces_position' => [
'anonymous_functions_opening_brace' => 'same_line',
],

// PHPDoc separation produces noisy blank lines around
// @return / @throws / @param. The existing house style packs
// them together; keep that.
'phpdoc_separation' => false,

// Array & syntax preferences (match the existing source).
'array_syntax' => ['syntax' => 'short'],
'trailing_comma_in_multiline' => ['elements' => ['arrays']],
'no_whitespace_before_comma_in_array' => true,
'whitespace_after_comma_in_array' => true,

// Imports.
'no_unused_imports' => true,
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => ['class', 'function', 'const'],
],

// Strings.
'single_quote' => true,

// Whitespace.
'blank_line_after_opening_tag' => true,
'no_extra_blank_lines' => [
'tokens' => [
'extra',
'throw',
'use',
'curly_brace_block',
'parenthesis_brace_block',
'square_brace_block',
],
],
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'single_blank_line_at_eof' => true,

// Operators.
'binary_operator_spaces' => ['default' => 'single_space'],
'concat_space' => ['spacing' => 'one'],
'not_operator_with_successor_space' => false,
'unary_operator_spaces' => true,

// Phpdoc.
'phpdoc_align' => ['align' => 'left'],
'phpdoc_indent' => true,
'phpdoc_no_useless_inheritdoc' => false, // @inheritDoc is meaningful for IDE/PHPStan in our interface impls.
'phpdoc_scalar' => true,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_trim' => true,
'phpdoc_trim_consecutive_blank_line_separation' => true,
]);
131 changes: 131 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Changelog

All notable changes to `initphp/events` are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Breaking changes

- **`EventEmitter` now honours priority.** Listeners are dispatched in
ascending numeric priority order regardless of the order in which
they were registered. In 1.x they ran in registration order; the
`ksort()` in `EventEmitter::listeners()` was applied to the wrong
array level and effectively did nothing. Code that registered
listeners in ascending priority order (the obvious style) sees no
visible change. Code that relied on the old "registration order
wins" behaviour now sees a different invocation order β€” that
reliance was almost certainly unintentional, but it is a
user-visible behaviour change.
- **`Event::on()` default priority changed** from
`Event::PRIORITY_LOW` (200) to `Event::PRIORITY_NORMAL` (100).
Calls that omitted the third argument now run earlier relative to
listeners registered with an explicit `PRIORITY_LOW`. Pass an
explicit priority to restore the old positioning.
- **`EventEmitterInterface` gained `clearOnceListeners(?string)`.**
Anyone shipping their own implementation of this interface must add
the method. The bundled `EventEmitter` of course implements it.
- **PHPUnit 9.6 requires PHP `>= 7.3`** for the dev environment.
Runtime `require` is unchanged (`php: >=5.6`); CI lints the source
on PHP 5.6 / 7.0 / 7.1 / 7.2 so the runtime contract is verified,
but the unit-test suite only runs on PHP 7.3 and newer.

### Added

- **`Event::once($name, $callback, $priority)`** β€” register a one-shot
listener via the high-level dispatcher (previously only available
on `EventEmitter`).
- **`Event::off($name, $callback)`** β€” remove a specific listener
(alias-style; forwards to `EventEmitter::removeListener()`).
- **`Event::removeAllListeners(?string $name)`** β€” wipe one event, or
every event when called with no arguments.
- **`Event::clearDebug()`** β€” empty the debug log without dropping the
dispatcher.
- **`Event::getEmitter()`** β€” expose the underlying `EventEmitter` for
callers that need both high-level dispatch and low-level emit on the
same listener registry.
- **`Events::reset()`** β€” drop the shared singleton so the next facade
call rebuilds a fresh one. Intended for test setUp/tearDown and
long-running processes that need a clean slate.
- **`Events::setInstance(Event $event)`** β€” inject a pre-configured
dispatcher, e.g. one with simulate or debug already toggled on.
- **`Events::getInstance()` is now public** (was `protected`).
- **`EventEmitter::clearOnceListeners(?string $event)`** β€” drop
one-shot listeners without invoking them. Used by the high-level
`Event::trigger()` loop to keep the once-contract intact when the
chain is stopped by a `false` return.

### Fixed

- **Once-listeners registered through `Event` (or `Events`) now fire
at most once.** In 1.x, `Event::trigger()` pulled the listener list
via `EventEmitter::listeners()` (which includes one-shot listeners)
but never cleaned them up, so they fired on every trigger. This is
now handled in a `try/finally` block, so the once contract is
honoured even when a listener throws or when the chain is halted by
a `false` return.
- **Typo / Turkish-only docblock on `Event::trigger()`** β€” replaced
with English documentation consistent with the rest of the
ecosystem.
- **`Event.php` license header** β€” was a different format and pointed
at a non-existent license URL; aligned with the rest of the
package.

### Internal / housekeeping

- Removed the empty `Event::__destruct()` and the defensive `isset()`
checks it forced on the getters; properties are always initialised
by the constructor.
- Removed redundant `(bool)` casts after the matching `is_bool()`
guards in `setSimulate()` / `setDebugMode()`.
- Replaced `::class` arguments to `class_exists()` / `interface_exists()`
in `src/aliases.php` with plain string literals β€” functionally
equivalent, but removes the compile-time constant dependency.
- 69 unit tests, 110 assertions covering the priority contract,
short-circuit semantics, simulate / debug modes, once + removal,
fluent API, exception paths, the static facade lifecycle, and the
backwards-compatibility alias for `\InitPHP\EventEmitter\*`.
**Coverage: 100% lines / 100% methods / 100% classes** across `src/`
(excluding `aliases.php`, which is verified by a dedicated BC alias
test instead).
- **PHPStan static analysis at level 8**, clean across `src/` and
`tests/`. Configuration in `phpstan.neon.dist`; runtime contract
preserved (no scalar type-hints injected). `composer analyse` runs it.
- New CI workflow (`.github/workflows/ci.yml`) with four jobs:
- `tests` β€” PHP 7.3 / 7.4 / 8.0 / 8.1 / 8.2 / 8.3 / 8.4 β€” `composer
install` + `phpunit`.
- `static-analysis` β€” PHPStan level 8 on PHP 8.3.
- `code-style` β€” PHP-CS-Fixer dry-run on PHP 8.3.
- `lowest-php-syntax` β€” PHP 5.6 / 7.0 / 7.1 / 7.2 β€” `php -l` on every
source file and a Composer-free autoload smoke test, to keep the
`composer.json: php >= 5.6` contract honest.
- `composer.json` now declares `autoload-dev` for the test suite,
`keywords`, `support` URLs, a `scripts.test` entry, and
`config.sort-packages`. Runtime `require` is unchanged.

## [1.0.2]

### Added

- Bundled the low-level `EventEmitter` primitive previously distributed
as the separate
[`initphp/event-emitter`](https://github.com/InitPHP/EventEmitter)
package, which is now deprecated. A class alias keeps the legacy
`\InitPHP\EventEmitter\*` fully-qualified names working.
- Minimum PHP requirement set to 5.6.

### Notes

- The standalone `initphp/event-emitter` package was retired; this
package declares a Composer `replace` for it.

## [Earlier]

Pre-1.0.2 history was not maintained in a `CHANGELOG.md`. Refer to the
Git log for individual fix commits (`git log src/Event.php`,
`git log src/Events.php`).

[Unreleased]: https://github.com/InitPHP/Events/compare/v1.0.2...HEAD
[1.0.2]: https://github.com/InitPHP/Events/releases/tag/v1.0.2
Loading
Loading