Skip to content

Commit bfaa31e

Browse files
Merge pull request #4 from InitPHP/2.x
2.x
2 parents 067f4fe + 87c4fa4 commit bfaa31e

85 files changed

Lines changed: 5253 additions & 1427 deletions

File tree

Some content is hidden

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

.github/dependabot.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: composer
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
open-pull-requests-limit: 5
8+
labels:
9+
- dependencies
10+
commit-message:
11+
prefix: chore
12+
include: scope
13+
14+
- package-ecosystem: github-actions
15+
directory: /
16+
schedule:
17+
interval: weekly
18+
open-pull-requests-limit: 5
19+
labels:
20+
- dependencies
21+
- ci
22+
commit-message:
23+
prefix: chore
24+
include: scope

.github/workflows/ci.yml

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- 2.x
8+
pull_request:
9+
branches:
10+
- main
11+
- 2.x
12+
13+
permissions:
14+
contents: read
15+
16+
concurrency:
17+
group: ci-${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
jobs:
21+
static-analysis:
22+
name: Static analysis & code style
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
28+
- name: Set up PHP
29+
uses: shivammathur/setup-php@v2
30+
with:
31+
php-version: '8.1'
32+
extensions: sockets, openssl
33+
coverage: none
34+
tools: composer:v2
35+
36+
- name: Get composer cache directory
37+
id: composer-cache
38+
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
39+
40+
- name: Cache composer dependencies
41+
uses: actions/cache@v4
42+
with:
43+
path: ${{ steps.composer-cache.outputs.dir }}
44+
key: composer-${{ runner.os }}-${{ hashFiles('composer.json') }}
45+
restore-keys: composer-${{ runner.os }}-
46+
47+
- name: Install dependencies
48+
run: composer install --no-interaction --no-progress --prefer-dist
49+
50+
- name: PHPStan
51+
run: composer stan -- --no-progress
52+
53+
- name: PHP-CS-Fixer (dry-run)
54+
run: composer cs-check
55+
56+
tests:
57+
name: Tests (PHP ${{ matrix.php }})
58+
runs-on: ubuntu-latest
59+
strategy:
60+
fail-fast: false
61+
matrix:
62+
php: ['8.1', '8.2', '8.3']
63+
steps:
64+
- name: Checkout
65+
uses: actions/checkout@v4
66+
67+
- name: Set up PHP
68+
uses: shivammathur/setup-php@v2
69+
with:
70+
php-version: ${{ matrix.php }}
71+
extensions: sockets, openssl, pcntl, posix
72+
coverage: pcov
73+
tools: composer:v2
74+
75+
- name: Get composer cache directory
76+
id: composer-cache
77+
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
78+
79+
- name: Cache composer dependencies
80+
uses: actions/cache@v4
81+
with:
82+
path: ${{ steps.composer-cache.outputs.dir }}
83+
key: composer-${{ runner.os }}-php${{ matrix.php }}-${{ hashFiles('composer.json') }}
84+
restore-keys: composer-${{ runner.os }}-php${{ matrix.php }}-
85+
86+
- name: Install dependencies
87+
run: composer install --no-interaction --no-progress --prefer-dist
88+
89+
- name: Run test suite
90+
run: composer test-coverage
91+
92+
- name: Upload coverage to Codecov
93+
if: matrix.php == '8.3'
94+
uses: codecov/codecov-action@v4
95+
with:
96+
files: build/coverage.xml
97+
fail_ci_if_error: false
98+
env:
99+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@
22
/.vs/
33
/.vscode/
44
/vendor/
5-
/composer.lock
5+
/composer.lock
6+
/build/
7+
/coverage/
8+
/.phpunit.cache/
9+
/.phpstan-cache/
10+
/.php-cs-fixer.cache

.php-cs-fixer.dist.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
$finder = PhpCsFixer\Finder::create()
6+
->in([__DIR__ . '/src', __DIR__ . '/tests'])
7+
->name('*.php')
8+
->ignoreDotFiles(true)
9+
->ignoreVCS(true);
10+
11+
return (new PhpCsFixer\Config())
12+
->setRiskyAllowed(true)
13+
->setRules([
14+
'@PSR12' => true,
15+
'@PSR12:risky' => true,
16+
'@PHP81Migration' => true,
17+
'declare_strict_types' => true,
18+
'native_function_invocation' => [
19+
'include' => ['@compiler_optimized'],
20+
'scope' => 'namespaced',
21+
'strict' => true,
22+
],
23+
'no_unused_imports' => true,
24+
'ordered_imports' => [
25+
'sort_algorithm' => 'alpha',
26+
'imports_order' => ['class', 'function', 'const'],
27+
],
28+
'single_quote' => true,
29+
'array_syntax' => ['syntax' => 'short'],
30+
'trailing_comma_in_multiline' => ['elements' => ['arrays', 'arguments', 'parameters']],
31+
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
32+
'phpdoc_align' => ['align' => 'left'],
33+
'phpdoc_order' => true,
34+
'phpdoc_separation' => true,
35+
])
36+
->setFinder($finder)
37+
->setCacheFile(__DIR__ . '/.php-cs-fixer.cache');

CHANGELOG.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6+
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/).
7+
8+
## [Unreleased]
9+
10+
## [2.0.0] — TBD
11+
12+
### Highlights
13+
14+
The 2.0 line is a clean break from 1.x — the previous server loop had
15+
race conditions, lost inbound data through its liveness check, and
16+
stored transport state on a `static` property that leaked between
17+
instances. The new shape ships with explicit enums, per-transport
18+
`Channel` strategies, a non-blocking `select`-driven loop and full
19+
PHP 8.1+ typing.
20+
21+
### Added
22+
23+
- **PHP 8.1+ enums**`Transport`, `Domain` and `CryptoMethod` replace
24+
magic integer / string flags.
25+
- **`ChannelInterface` + `TcpChannel` / `UdpChannel` / `StreamChannel`**
26+
per-transport I/O strategy. `ServerConnection` is now just identity
27+
plus delegation.
28+
- **`SocketExceptionInterface`** — marker implemented by every exception
29+
in the package, so a single catch covers them all.
30+
- **`tick(callable, float): int`** — single-iteration accept/dispatch
31+
method on every server. Use it to embed the package in your own
32+
event loop or to drive servers deterministically in tests.
33+
- **`stop()` / `isRunning()`** — cooperative shutdown for the `live()`
34+
loop.
35+
- **`register(int|string, SocketConnectionInterface): bool`** — promoted
36+
to `SocketServerInterface`; the 1.x package-private `clientRegister()`
37+
is replaced by an interface method with a stable contract.
38+
- **PHPUnit 10 test suite** — 36 unit + integration tests covering
39+
enums, exception hierarchy, factory, channels, broadcast/register
40+
logic, TCP echo, UDP per-peer routing, TLS handshake (forked).
41+
- **CI pipeline** — GitHub Actions matrix across PHP 8.1, 8.2, 8.3 with
42+
PHPStan level 8, PHP-CS-Fixer and Codecov upload.
43+
- **`docs/` directory** — getting started, architecture, per-transport
44+
server and client guides, cookbook (chat server, raw SMTP) and the
45+
migration guide.
46+
47+
### Changed
48+
49+
- **Minimum PHP version** is now `^8.1` (was `>=7.4`).
50+
- **`composer.json`** now requires `ext-openssl` in addition to
51+
`ext-sockets`.
52+
- **Server method renames**`connection()``listen()`,
53+
`disconnect()``close()`. `live()` signature now takes
54+
`float $idleSeconds` instead of `int $usleep`. `wait()` is typed
55+
`float $seconds`.
56+
- **`SocketServerClientInterface` renamed to `SocketConnectionInterface`.**
57+
`push()` is now `write()`. `isDisconnected()` is replaced by the
58+
non-destructive `isAlive()`.
59+
- **Exception hierarchy**`SocketException` extends
60+
`\RuntimeException`. `SocketConnectionException` and
61+
`SocketListenException` now extend `SocketException` (previously
62+
`\Exception`). `SocketInvalidArgumentException` still extends
63+
`\InvalidArgumentException` and additionally implements
64+
`SocketExceptionInterface`.
65+
66+
### Removed
67+
68+
- `Common/BaseClient`, `Common/BaseCommon`, `Common/BaseServer`,
69+
`Common/ServerTrait`, `Common/StreamClientTrait`,
70+
`Common/StreamServerTrait` — replaced by `Client/AbstractClient`,
71+
`Client/AbstractStreamClient`, `Server/AbstractServer` and
72+
`Server/AbstractStreamServer`.
73+
- `Server/ServerClient` — replaced by `Server/ServerConnection`.
74+
- `Interfaces/SocketServerClientInterface` — replaced by
75+
`Interfaces/SocketConnectionInterface`.
76+
- `Socket::TCP`, `Socket::UDP`, `Socket::TLS`, `Socket::SSL` integer
77+
constants — replaced by the `Transport` enum.
78+
- The mystery-typed `$argument` parameter on the factory and on every
79+
constructor — replaced by explicit `?Domain $domain` / `?float $timeout`
80+
named parameters.
81+
- All `__setSocket()` / `__setCallbacks()` / `__removeCallbacks()`
82+
magic-prefixed methods.
83+
- The static `ServerClient::$credentials` array that leaked transport
84+
state between server instances.
85+
- `echo` calls inside `ServerClient` that wrote
86+
`"New client connected."` / `"Client disconnected."` to STDOUT.
87+
88+
### Fixed
89+
90+
- **Server loop accepts more than one client.** The 1.x `connection()`
91+
performed a blocking `socket_accept()` before `live()` was even
92+
called, capping the server at a single connection per process.
93+
- **TLS / SSL servers use the right accept call.** The 1.x loop called
94+
`socket_accept()` on a stream resource (always wrong for TLS / SSL)
95+
and never accepted the second connection.
96+
- **Liveness checks no longer consume data.** The 1.x
97+
`isDisconnected()` read a line off every client every iteration
98+
and discarded it, so application-level reads never saw any data.
99+
- **Client id map survives disconnects.** The 1.x `clientMap` stored
100+
raw array indices that became dangling after `unset()` on a
101+
disconnected client. The new `clientIdMap` uses monotonic keys and
102+
cleans up on eviction.
103+
- **UDP server demultiplexes peers correctly.** The 1.x server
104+
registered the listening socket itself as a "client" and broadcast
105+
back to its own host/port.
106+
- **TLS handshake has enough time.** The 1.x server left the listening
107+
stream non-blocking and called `stream_socket_accept(..., 0.0)`,
108+
which prevented the handshake from completing under load. The new
109+
loop keeps the listening stream blocking and drives readiness via
110+
`stream_select`, giving the handshake the full timeout.
111+
112+
### Migration
113+
114+
See [`docs/migration-1.x-to-2.x.md`](./docs/migration-1.x-to-2.x.md) for
115+
a step-by-step upgrade guide.
116+
117+
[Unreleased]: https://github.com/InitPHP/Socket/compare/v2.0.0...HEAD
118+
[2.0.0]: https://github.com/InitPHP/Socket/releases/tag/v2.0.0

0 commit comments

Comments
 (0)