AbstractConnection   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
wmc 18
lcom 2
cbo 4
dl 0
loc 147
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
connect() 0 1 ?
A __destruct() 0 4 1
A addListener() 0 4 1
A fireEvent() 0 10 3
A disconnect() 0 7 2
A timeout() 0 4 1
A send() 0 10 2
A receive() 0 12 2
A getMetaData() 0 16 1
A verifyConnection() 0 8 2
A verifyAlive() 0 14 3
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Protocol;
5
6
use Genkgo\Mail\Exception\CannotWriteToStreamException;
7
use Genkgo\Mail\Exception\ConnectionBrokenException;
8
use Genkgo\Mail\Exception\ConnectionTimeoutException;
9
use Genkgo\Mail\Exception\ConnectionClosedException;
10
11
/**
12
 * @codeCoverageIgnore
13
 */
14
abstract class AbstractConnection implements ConnectionInterface
15
{
16
    private const RECEIVE_BYTES = 1024;
17
18
    /**
19
     * @var resource|null
20
     */
21
    protected $resource;
22
23
    /**
24
     * @var array<string, array<int, \Closure>>
25
     */
26
    private $listeners = [
27
        'connect' => []
28
    ];
29
    
30
    final public function __destruct()
31
    {
32
        $this->disconnect();
33
    }
34
35
    /**
36
     * @param string $name
37
     * @param \Closure $callback
38
     */
39
    final public function addListener(string $name, \Closure $callback): void
40
    {
41
        $this->listeners[$name][] = $callback;
42
    }
43
44
    /**
45
     * @param string $name
46
     */
47
    final protected function fireEvent(string $name): void
48
    {
49
        if (!isset($this->listeners[$name])) {
50
            return;
51
        }
52
53
        foreach ($this->listeners[$name] as $listener) {
54
            $listener();
55
        }
56
    }
57
    
58
    abstract public function connect(): void;
59
    
60
    final public function disconnect(): void
61
    {
62
        if ($this->resource !== null) {
63
            \fclose($this->resource);
64
            $this->resource = null;
65
        }
66
    }
67
68
    /**
69
     * @param float $timeout
70
     */
71
    final public function timeout(float $timeout): void
72
    {
73
        \stream_set_timeout($this->verifyConnection(), (int)$timeout);
74
    }
75
76
    /**
77
     * @param string $request
78
     * @return int
79
     * @throws CannotWriteToStreamException
80
     */
81
    final public function send(string $request): int
82
    {
83
        $bytesWritten = \fwrite($this->verifyConnection(), $request);
84
85
        if ($bytesWritten === false) {
86
            throw new CannotWriteToStreamException();
87
        }
88
89
        return $bytesWritten;
90
    }
91
92
    /**
93
     * @return string
94
     */
95
    final public function receive(): string
96
    {
97
        $response = \fgets($this->verifyConnection(), self::RECEIVE_BYTES);
98
99
        $this->verifyAlive();
100
101
        if ($response === false) {
102
            throw new ConnectionBrokenException('Cannot receive information');
103
        }
104
105
        return $response;
106
    }
107
108
    /**
109
     * @param array<int, string> $keys
110
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
111
     */
112
    final public function getMetaData(array $keys = []): array
113
    {
114
        $resource = $this->verifyAlive();
115
116
        $metaData = \stream_get_meta_data($resource);
117
118
        $keys = \array_map('strtolower', $keys);
119
120
        return \array_filter(
121
            $metaData,
122
            function ($key) use ($keys) {
123
                return \in_array(\strtolower($key), $keys);
124
            },
125
            ARRAY_FILTER_USE_KEY
126
        );
127
    }
128
129
    /**
130
     * @return resource
131
     */
132
    private function verifyConnection()
133
    {
134
        if ($this->resource === null) {
135
            throw new \UnexpectedValueException('Cannot communicate when there is no connection');
136
        }
137
138
        return $this->resource;
139
    }
140
141
    /**
142
     * @return resource
143
     * @throws ConnectionClosedException
144
     * @throws ConnectionTimeoutException
145
     */
146
    private function verifyAlive()
147
    {
148
        $resource = $this->verifyConnection();
149
        $info = \stream_get_meta_data($resource);
150
        if ($info['timed_out']) {
151
            throw new ConnectionTimeoutException('Connection has timed out');
152
        }
153
154
        if ($info['eof']) {
155
            throw new ConnectionClosedException('Connection is gone');
156
        }
157
158
        return $resource;
159
    }
160
}
161