diff --git a/composer.json b/composer.json
index 5a621910..e181d21c 100644
--- a/composer.json
+++ b/composer.json
@@ -28,6 +28,7 @@
"laminas/laminas-servicemanager": "^3.7",
"phpunit/phpunit": "^9.5.5",
"psalm/plugin-phpunit": "^0.15.1",
+ "symfony/process": "^5.3.7",
"vimeo/psalm": "^4.7"
},
"suggest": {
diff --git a/composer.lock b/composer.lock
index 85bc1461..cd845781 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "1a7c0799aa0d9458ce5b1e553f90fc4e",
+ "content-hash": "5831a1c6e28db10afe471ae508c67b81",
"packages": [
{
"name": "container-interop/container-interop",
@@ -1324,16 +1324,16 @@
},
{
"name": "laminas/laminas-db",
- "version": "2.13.3",
+ "version": "2.13.4",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-db.git",
- "reference": "e1bcf243f6e56f02590f11a149cd75403e873241"
+ "reference": "cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laminas/laminas-db/zipball/e1bcf243f6e56f02590f11a149cd75403e873241",
- "reference": "e1bcf243f6e56f02590f11a149cd75403e873241",
+ "url": "https://api.github.com/repos/laminas/laminas-db/zipball/cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1",
+ "reference": "cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1",
"shasum": ""
},
"require": {
@@ -1391,7 +1391,7 @@
"type": "community_bridge"
}
],
- "time": "2021-09-19T07:38:14+00:00"
+ "time": "2021-09-21T18:59:44+00:00"
},
{
"name": "laminas/laminas-math",
@@ -4238,6 +4238,68 @@
],
"time": "2021-07-28T13:41:28+00:00"
},
+ {
+ "name": "symfony/process",
+ "version": "v5.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/38f26c7d6ed535217ea393e05634cb0b244a1967",
+ "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v5.3.7"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-08-04T21:20:46+00:00"
+ },
{
"name": "symfony/service-contracts",
"version": "v2.4.0",
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index f7abf843..8cd51f52 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -2751,6 +2751,12 @@
$sender
+
+
+
+ $type
+
+
ProtocolTrait::class
diff --git a/src/Protocol/AbstractProtocol.php b/src/Protocol/AbstractProtocol.php
index 394223b0..304dfe15 100644
--- a/src/Protocol/AbstractProtocol.php
+++ b/src/Protocol/AbstractProtocol.php
@@ -290,7 +290,7 @@ protected function _receive($timeout = null)
// Check meta data to ensure connection is still valid
$info = stream_get_meta_data($this->socket);
- if (! $info['timed_out']) {
+ if ($info['timed_out']) {
throw new Exception\RuntimeException($this->host . ' has timed out');
}
diff --git a/test/Protocol/AbstractProtocolTest.php b/test/Protocol/AbstractProtocolTest.php
new file mode 100644
index 00000000..dbf6a7ac
--- /dev/null
+++ b/test/Protocol/AbstractProtocolTest.php
@@ -0,0 +1,81 @@
+
+ */
+final class AbstractProtocolTest extends TestCase
+{
+ /** @var Process */
+ private $process;
+
+ protected function setUp(): void
+ {
+ $this->process = new Process([
+ PHP_BINARY,
+ '-S',
+ '127.0.0.1:8080',
+ '-t',
+ __DIR__ . '/HttpStatusService',
+ ]);
+ $this->process->start();
+ $this->process->waitUntil(static function (string $type, string $output): bool {
+ return false !== strpos($output, 'started');
+ });
+ }
+
+ protected function tearDown(): void
+ {
+ $this->process->stop();
+ }
+
+ /**
+ * @requires PHP >= 7.4
+ */
+ public function testExceptionShouldBeRaisedWhenConnectionHasTimedOut(): void
+ {
+ $protocol = new class('127.0.0.1', 8080) extends AbstractProtocol {
+ use ProtocolTrait;
+
+ public function connect(): void
+ {
+ $this->_disconnect();
+ $this->socket = $this->setupSocket('tcp', $this->host, $this->port, 2);
+ }
+
+ public function send(string $path, ?int $readTimeout): string
+ {
+ $this->_send('GET ' . $path . ' HTTP/1.1');
+ $this->_send('Host: ' . $this->host);
+ $this->_send('');
+
+ return $this->_receive($readTimeout);
+ }
+ };
+
+ $protocol->connect();
+ self::assertSame('HTTP/1.1 200 OK' . AbstractProtocol::EOL, $protocol->send('/', null));
+
+ $protocol->connect();
+ $this->expectExceptionObject(new \Laminas\Mail\Protocol\Exception\RuntimeException('127.0.0.1 has timed out'));
+ $protocol->send('/?sleep=3', 1);
+ }
+}
diff --git a/test/Protocol/HttpStatusService/index.php b/test/Protocol/HttpStatusService/index.php
new file mode 100644
index 00000000..cfc31d48
--- /dev/null
+++ b/test/Protocol/HttpStatusService/index.php
@@ -0,0 +1,7 @@
+