Completed
Push — master ( a1eef3...5d4e59 )
by Frederik
02:09
created

GenericMessage   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 18
lcom 2
cbo 6
dl 0
loc 184
ccs 63
cts 63
cp 1
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getHeaders() 0 4 1
A hasHeader() 0 5 2
A getHeader() 0 10 2
A withHeader() 0 6 1
A withAddedHeader() 0 6 1
A withoutHeader() 0 6 1
A withBody() 0 6 1
A getBody() 0 4 1
A __toString() 0 23 1
B fromString() 0 30 6
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail;
5
6
use Genkgo\Mail\Header\HeaderLine;
7
use Genkgo\Mail\Header\MimeVersion;
8
use Genkgo\Mail\Stream\EmptyStream;
9
use Genkgo\Mail\Stream\BitEncodedStream;
10
11
/**
12
 * Class ImmutableMessage
13
 * @package Genkgo\Mail
14
 */
15
final class GenericMessage implements MessageInterface
16
{
17
    /**
18
     * @var array
19
     */
20
    private $headers = [
21
        'return-path' => [],
22
        'received' => [],
23
        'dkim-signature' => [],
24
        'domainkey-signature' => [],
25
        'sender' => [],
26
        'message-id' => [],
27
        'date' => [],
28
        'subject' => [],
29
        'from' => [],
30
        'reply-to' => [],
31
        'to' => [],
32
        'cc' => [],
33
        'bcc' => [],
34
        'mime-version' => [],
35
        'content-type' => [],
36
        'content-transfer-encoding' => [],
37
    ];
38
    /**
39
     * @var StreamInterface
40
     */
41
    private $body;
42
43
    /**
44
     * ImmutableMessage constructor.
45
     */
46 56
    public function __construct()
47
    {
48 56
        $this->body = new EmptyStream();
49 56
        $this->headers['mime-version'] = [new MimeVersion()];
50 56
    }
51
52
    /**
53
     * @return iterable
54
     */
55 5
    public function getHeaders(): iterable
56
    {
57 5
        return $this->headers;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->headers; (array) is incompatible with the return type declared by the interface Genkgo\Mail\MessageInterface::getHeaders of type Genkgo\Mail\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
58
    }
59
60
    /**
61
     * @param string $name
62
     * @return bool
63
     */
64 16
    public function hasHeader(string $name): bool
65
    {
66 16
        $name = strtolower($name);
67 16
        return isset($this->headers[$name]) && $this->headers[$name];
68
    }
69
70
    /**
71
     * @param string $name
72
     * @return HeaderInterface[]|iterable
73
     */
74 16
    public function getHeader(string $name): iterable
75
    {
76 16
        $name = strtolower($name);
77
78 16
        if (!isset($this->headers[$name])) {
79 1
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type declared by the interface Genkgo\Mail\MessageInterface::getHeader of type Genkgo\Mail\iterable|Genkgo\Mail\HeaderInterface[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
80
        }
81
82 16
        return $this->headers[$name];
83
    }
84
85
    /**
86
     * @param HeaderInterface $header
87
     * @return MessageInterface
88
     */
89 42
    public function withHeader(HeaderInterface $header): MessageInterface
90
    {
91 42
        $clone = clone $this;
92 42
        $clone->headers[strtolower((string)$header->getName())] = [$header];
93 42
        return $clone;
94
    }
95
96
    /**
97
     * @param HeaderInterface $header
98
     * @return MessageInterface
99
     */
100 3
    public function withAddedHeader(HeaderInterface $header): MessageInterface
101
    {
102 3
        $clone = clone $this;
103 3
        $clone->headers[strtolower((string)$header->getName())][] = $header;
104 3
        return $clone;
105
    }
106
107
    /**
108
     * @param string $name
109
     * @return MessageInterface
110
     */
111 5
    public function withoutHeader(string $name): MessageInterface
112
    {
113 5
        $clone = clone $this;
114 5
        unset($clone->headers[(string)strtolower($name)]);
115 5
        return $clone;
116
    }
117
118
    /**
119
     * @param StreamInterface $body
120
     * @return MessageInterface
121
     */
122 27
    public function withBody(StreamInterface $body): MessageInterface
123
    {
124 27
        $clone = clone $this;
125 27
        $clone->body = $body;
126 27
        return $clone;
127
    }
128
129
    /**
130
     * @return StreamInterface
131
     */
132 3
    public function getBody(): StreamInterface
133
    {
134 3
        return $this->body;
135
    }
136
137
    /**
138
     * @return string
139
     */
140 21
    public function __toString(): string
141
    {
142 21
        $headerString = array_values(
143 21
            array_filter(
144 21
                array_map(
145
                    function (array $headers) {
146 21
                        return implode(
147 21
                            "\r\n",
148 21
                            array_map(
149 21
                                function (HeaderInterface $header) {
150 21
                                    return (string) (new HeaderLine($header));
151 21
                                },
152 21
                                $headers
153
                            )
154
                        );
155 21
                    },
156 21
                    $this->headers
157
                )
158
            )
159
        );
160
161 21
        return implode("\r\n", array_merge($headerString, ['', (string)$this->body]));
162
    }
163
164
    /**
165
     * @param string $messageString
166
     * @return MessageInterface
167
     */
168 11
    public static function fromString(string $messageString): MessageInterface
169
    {
170 11
        $message = new self();
171
172 11
        $lines = preg_split('/\r\n/', $messageString);
173 11
        for ($n = 0, $length = count($lines); $n < $length; $n++) {
174 11
            $line = $lines[$n];
175
176 11
            if ($line === '') {
177 11
                $message = $message->withBody(
178 11
                    new BitEncodedStream(
179 11
                        implode(
180 11
                            "\r\n",
181 11
                            array_slice($lines, $n + 1)
182
                        )
183
                    )
184
                );
185 11
                break;
186
            }
187
188 11
            while (isset($lines[$n + 1]) && $lines[$n + 1] !== '' && $lines[$n + 1][0] === ' ') {
189 1
                $line .= $lines[$n + 1];
190 1
                $n++;
191
            }
192
193 11
            $message = $message->withHeader(HeaderLine::fromString($line)->getHeader());
194
        }
195
196 11
        return $message;
197
    }
198
}
199