Skip to content
Open
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
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"pestphp/pest": "^4.0",
"pestphp/pest-plugin-arch": "^4.0",
"pestphp/pest-plugin-laravel": "^4.0",
"phpseclib/phpseclib": "^4.0.1",
"phpseclib/phpseclib": "^4.0.3",
"phpstan/phpstan": "^2.0",
"rector/rector": "^2.5.8",
"roave/security-advisories": "dev-latest",
Expand All @@ -78,7 +78,7 @@
"laravel/passport": "OAuth2 guard for the optional REST API (service-to-service access).",
"laravel/sanctum": "Default token guard for the optional REST API.",
"laravel/slack-notification-channel": "Sends notifications and alerts to Slack (notifications.channels with slack).",
"phpseclib/phpseclib": "Required by the phpseclib SSH tunnel driver (^4.0.1), for hosts without an ssh binary.",
"phpseclib/phpseclib": "Required by the phpseclib SSH tunnel driver (^4.0.3), for hosts without an ssh binary.",
"spatie/laravel-permission": "Delegates console RBAC storage to Spatie when rbac.driver is set to spatie."
},
"autoload": {
Expand Down
12 changes: 12 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;
use Rector\DeadCode\Rector\FunctionLike\NarrowWideUnionReturnTypeRector;

return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
Expand All @@ -17,4 +18,15 @@
SetList::TYPE_DECLARATION,
SetList::EARLY_RETURN,
])
// NarrowWideUnionReturnTypeRector cannot see a property hook. ForwardingSsh2::$curTimeout has a
// `set` hook that rewrites 5 to POLL_TIMEOUT (0.005) while polling, so the closure in
// ForwardingSsh2Test genuinely returns int|float -- and the test asserts both, 0.005 and 5.
// Rector infers `int` from the literal assignment alone; applying that suggestion throws
// "Return value must be of type int, float returned", verified 2026-11-13 by making the change
// and watching the test fail. Scoped to the one file, so the rule still runs everywhere else.
->withSkip([
NarrowWideUnionReturnTypeRector::class => [
__DIR__ . '/tests/Unit/Tunnels/ForwardingSsh2Test.php',
],
])
->withImportNames(removeUnusedImports: true);
10 changes: 7 additions & 3 deletions src/Tunnels/Phpseclib/ForwardingSsh2.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ final class ForwardingSsh2 extends SSH2
* ran out. While polling, the wait is never raised past the poll's own.
* The hook applies to writes from phpseclib's code too, which is the point.
*/
protected int|float|null $curTimeout = null {
set(int|float|null $value) {
$this->curTimeout = $this->polling && $value !== null && $value > self::POLL_TIMEOUT
protected int|float|false $curTimeout = false {
set(int|float|false $value) {
// `false` is phpseclib's "no timeout", as `null` was before 4.0.3. It is excluded from
// the comparison for the same reason null was: it is not a duration, so `> POLL_TIMEOUT`
// would be meaningless. An overriding property MUST repeat the parent's type exactly,
// so this declaration tracks SSH2::$curTimeout rather than expressing a preference.
$this->curTimeout = $this->polling && $value !== false && $value > self::POLL_TIMEOUT
? self::POLL_TIMEOUT
: $value;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Tunnels/ForwardingSsh2Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// Lazy: the constructor connects nothing until a method needs the socket.
$ssh = new ForwardingSsh2('ssh.invalid', 22, 10);

$timeoutAfterClose = Closure::bind(function (bool $polling): int|float|null {
$timeoutAfterClose = Closure::bind(function (bool $polling): int|float {
$this->polling = $polling;
$this->curTimeout = 5; // what get_channel_packet() does on a CHANNEL_CLOSE

Expand Down
Loading