Completed
Push — master ( 251ec5...d73f1a )
by Frederik
02:24
created

GenericPart::fromMessage()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 9
cts 9
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 1
crap 4
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Mime;
5
6
use Genkgo\Mail\HeaderInterface;
7
use Genkgo\Mail\MessageInterface;
8
use Genkgo\Mail\Stream\EmptyStream;
9
use Genkgo\Mail\StreamInterface;
10
11
final class GenericPart implements PartInterface
12
{
13
    private const ALLOWED_HEADERS = [
14
        'content-type' => true,
15
        'content-transfer-encoding' => true,
16
        'content-id' => true,
17
        'content-disposition' => true,
18
        'content-description' => true,
19
        'content-location' => true,
20
        'content-language' => true,
21
    ];
22
23
    /**
24
     * @var array|HeaderInterface[]
25
     */
26
    private $headers = [];
27
28
    /**
29
     * @var StreamInterface
30
     */
31
    private $body;
32
    
33 51
    public function __construct()
34
    {
35 51
        $this->body = new EmptyStream();
36 51
    }
37
38
    /**
39
     * @return iterable
40
     */
41 14
    public function getHeaders(): iterable
42
    {
43 14
        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\Mime\PartInterface::getHeaders of type Genkgo\Mail\Mime\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...
44
    }
45
46
    /**
47
     * @param string $name
48
     * @return bool
49
     */
50 10
    public function hasHeader(string $name): bool
51
    {
52 10
        $name = \strtolower($name);
53
54 10
        return isset($this->headers[$name]);
55
    }
56
57
    /**
58
     * @param string $name
59
     * @return HeaderInterface
60
     */
61 24
    public function getHeader(string $name): HeaderInterface
62
    {
63 24
        $name = \strtolower($name);
64
65 24
        if (!isset($this->headers[$name])) {
66 5
            throw new \UnexpectedValueException('No header with name ' . $name);
67
        }
68
69 23
        return $this->headers[$name];
70
    }
71
72
    /**
73
     * @param HeaderInterface $header
74
     * @return PartInterface
75
     */
76 51
    public function withHeader(HeaderInterface $header): PartInterface
77
    {
78 51
        $name = \strtolower((string)$header->getName());
79 51
        $this->assertValidHeader($name);
80
81 50
        $clone = clone $this;
82 50
        $clone->headers[$name] = $header;
83 50
        return $clone;
84
    }
85
86
    /**
87
     * @param string $name
88
     * @return PartInterface
89
     */
90 7
    public function withoutHeader(string $name): PartInterface
91
    {
92 7
        $name = \strtolower($name);
93
94 7
        $clone = clone $this;
95 7
        unset($clone->headers[$name]);
96 7
        return $clone;
97
    }
98
99
    /**
100
     * @param StreamInterface $body
101
     * @return PartInterface
102
     */
103 40
    public function withBody(StreamInterface $body): PartInterface
104
    {
105 40
        $clone = clone $this;
106 40
        $clone->body = $body;
107 40
        return $clone;
108
    }
109
110
    /**
111
     * @return StreamInterface
112
     */
113 15
    public function getBody(): StreamInterface
114
    {
115 15
        return $this->body;
116
    }
117
118
    /**
119
     * @param string $name
120
     */
121 51
    private function assertValidHeader(string $name)
122
    {
123 51
        if (!isset(self::ALLOWED_HEADERS[$name])) {
124 1
            throw new \InvalidArgumentException('Invalid Mime part header ' . $name);
125
        }
126 50
    }
127
128
    /**
129
     * @param MessageInterface $message
130
     * @return GenericPart
131
     */
132 5
    public static function fromMessage(MessageInterface $message): self
133
    {
134 5
        $part = new self();
135 5
        foreach ($message->getHeaders() as $headers) {
136 5
            foreach ($headers as $header) {
137 5
                $headerName = \strtolower((string)$header->getName());
138 5
                if (isset(self::ALLOWED_HEADERS[$headerName])) {
139 5
                    $part->headers[$headerName] = $header;
140
                }
141
            }
142
        }
143
144 5
        $part->body = $message->getBody();
145 5
        return $part;
146
    }
147
}
148