Completed
Push — master ( f7bed6...7b8b9a )
by Gabriel
05:48 queued 11s
created

SendgridTransport::setApiKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
crap 1
1
<?php
2
3
namespace Nip\Mail\Transport;
4
5
use Exception;
6
use Html2Text\Html2Text;
7
use Nip\Mail\Message;
8
use SendGrid;
9
use SendGrid\Attachment;
10
use SendGrid\Content;
11
use SendGrid\Email;
12
use SendGrid\Mail;
13
use SendGrid\Personalization;
14
use SendGrid\ReplyTo;
15
use Swift_Attachment;
16
use Swift_Image;
17
use Swift_Mime_Message as MessageInterface;
18
use Swift_MimePart;
19
use Swift_TransportException;
20
21
/**
22
 * Class SendgridTransport
23
 * @package Nip\Mail\Transport
24
 */
25
class SendgridTransport extends AbstractTransport
26
{
27
    /** @var string|null */
28
    protected $apiKey;
29
30
    /**
31
     * @var null|Mail|MessageInterface
32
     */
33
    protected $mail = null;
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function send(\Swift_Mime_Message $message, &$failedRecipients = null)
39
    {
40
        $this->initMail();
41
42
        $this->populateSenders($message);
43
        $this->populatePersonalization($message);
44
        $this->populateContent($message);
45
        $this->populateCustomArg($message);
46
47
        $sg = $this->createApi();
48
49
        /** @noinspection PhpUndefinedMethodInspection */
50
        /** @var SendGrid\Response $response */
51
        $response = $sg->client->mail()->send()->post($this->getMail());
52
53
        if ($response->statusCode() == '202') {
54
            return 1;
55
        } else {
56
            throw new Swift_TransportException(
57
                'Error sending email Code [' . $response->statusCode() . ']. ' .
58
                $response->body() . $response->headers()
59
            );
60
        }
61
    }
62
63
    public function initMail()
64
    {
65
        $this->setMail(new Mail());
66
    }
67
68
    /**
69
     * @param Message|MessageInterface $message
70
     */
71
    protected function populateSenders($message)
72
    {
73
        $from = $message->getFrom();
74
        foreach ($from as $address => $name) {
75
            $email = new Email($name, $address);
76
            $this->getMail()->setFrom($email);
77
78
            $reply_to = new ReplyTo($address);
79
            $this->getMail()->setReplyTo($reply_to);
80
        }
81
    }
82
83
    /**
84
     * @return null|Mail
85
     */
86
    public function getMail()
87
    {
88
        return $this->mail;
89
    }
90
91
    /**
92
     * @param null|Mail $mail
93
     */
94
    public function setMail($mail)
95
    {
96
        $this->mail = $mail;
97
    }
98
99
    /**
100
     * @param Message|MessageInterface $message
101
     * @throws Exception
102
     */
103
    protected function populatePersonalization($message)
104
    {
105
        $emailsTos = $message->getTo();
106
        if (!is_array($emailsTos) or count($emailsTos) < 1) {
107
            throw new Exception('Cannot send email withought reciepients');
108
        }
109
        $i = 0;
110
        foreach ($emailsTos as $emailTo => $nameTo) {
111
            $personalization = $this->generatePersonalization($emailTo, $nameTo, $message, $i);
0 ignored issues
show
Compatibility introduced by
$message of type object<Swift_Mime_Message> is not a sub-type of object<Nip\Mail\Message>. It seems like you assume a concrete implementation of the interface Swift_Mime_Message to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
112
            $this->getMail()->addPersonalization($personalization);
0 ignored issues
show
Bug introduced by
The method addPersonalization does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
113
            $i++;
114
        }
115
    }
116
117
    /**
118
     * @param $emailTo
119
     * @param $nameTo
120
     * @param Message $message
121
     * @param integer $i
122
     * @return Personalization
123
     */
124
    protected function generatePersonalization($emailTo, $nameTo, $message, $i)
125
    {
126
        $personalization = new Personalization();
127
128
        $email = new Email($nameTo, $emailTo);
129
        $personalization->addTo($email);
130
131
        $personalization->setSubject($message->getSubject());
132
133
        $mergeTags = $message->getMergeTags();
134
        foreach ($mergeTags as $varKey => $value) {
135
            if (is_array($value)) {
136
                $value = $value[$i];
137
            }
138
            $value = (string) $value;
139
            $personalization->addSubstitution('{{' . $varKey . '}}', $value);
140
        }
141
142
        return $personalization;
143
    }
144
145
    /**
146
     * @param Message|MessageInterface $message
147
     */
148
    protected function populateContent($message)
149
    {
150
        $contentType = $this->getMessagePrimaryContentType($message);
151
152
        $bodyHtml = $bodyText = null;
0 ignored issues
show
Unused Code introduced by
$bodyText is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
153
154
        if ($contentType === 'text/plain') {
155
            $bodyText = $message->getBody();
156
        } else {
157
            $bodyHtml = $message->getBody();
158
            $bodyText = (new Html2Text($bodyHtml))->getText();
159
        }
160
161
        foreach ($message->getChildren() as $child) {
162
            if ($child instanceof Swift_Image) {
163
                $images[] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$images was never initialized. Although not strictly required by PHP, it is generally a good practice to add $images = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
164
                    'type' => $child->getContentType(),
165
                    'name' => $child->getId(),
166
                    'content' => base64_encode($child->getBody()),
167
                ];
168
            } elseif ($child instanceof Swift_Attachment && !($child instanceof Swift_Image)) {
169
                $this->addAttachment($child);
170
            } elseif ($child instanceof Swift_MimePart && $this->supportsContentType($child->getContentType())) {
171
                if ($child->getContentType() == "text/html") {
172
                    $bodyHtml = $child->getBody();
173
                } elseif ($child->getContentType() == "text/plain") {
174
                    $bodyText = $child->getBody();
175
                }
176
            }
177
        }
178
179
        $content = new Content("text/plain", $bodyText);
180
        $this->getMail()->addContent($content);
0 ignored issues
show
Bug introduced by
The method addContent does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
181
182
        $content = new Content("text/html", $bodyHtml);
183
        $this->getMail()->addContent($content);
184
    }
185
186
    /**
187
     * @param MessageInterface $message
188
     * @return string
189
     */
190
    protected function getMessagePrimaryContentType(MessageInterface $message)
191
    {
192
        $contentType = $message->getContentType();
193
        if ($this->supportsContentType($contentType)) {
194
            return $contentType;
195
        }
196
        // SwiftMailer hides the content type set in the constructor of Swift_Mime_Message as soon
197
        // as you add another part to the message. We need to access the protected property
198
        // _userContentType to get the original type.
199
        $messageRef = new \ReflectionClass($message);
200
        if ($messageRef->hasProperty('_userContentType')) {
201
            $propRef = $messageRef->getProperty('_userContentType');
202
            $propRef->setAccessible(true);
203
            $contentType = $propRef->getValue($message);
204
        }
205
206
        return $contentType;
207
    }
208
209
    /**
210
     * @param string $contentType
211
     * @return bool
212
     */
213
    protected function supportsContentType($contentType)
214
    {
215
        return in_array($contentType, $this->getSupportedContentTypes());
216
    }
217
218
    /**
219
     * @return string[]
220
     */
221
    protected function getSupportedContentTypes()
222
    {
223
        return [
224
            'text/plain',
225
            'text/html',
226
        ];
227
    }
228
229
    /**
230
     * @param Swift_Attachment $attachment
231
     */
232
    protected function addAttachment($attachment)
233
    {
234
        $sgAttachment = new Attachment();
235
        $sgAttachment->setContent(base64_encode($attachment->getBody()));
236
        $sgAttachment->setType($attachment->getContentType());
237
        $sgAttachment->setFilename($attachment->getFilename());
238
        $sgAttachment->setDisposition("attachment");
239
        $sgAttachment->setContentID($attachment->getId());
240
        $this->getMail()->addAttachment($sgAttachment);
0 ignored issues
show
Bug introduced by
The method addAttachment does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
241
    }
242
243
    /**
244
     * @param Message|MessageInterface $message
245
     */
246
    protected function populateCustomArg($message)
247
    {
248
        $args = $message->getCustomArgs();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Swift_Mime_Message as the method getCustomArgs() does only exist in the following implementations of said interface: Nip\Mail\Message.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
249
        foreach ($args as $key => $value) {
250
            if ($key == 'category') {
251
                $this->getMail()->addCategory($value);
0 ignored issues
show
Bug introduced by
The method addCategory does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
252
            } else {
253
                $this->getMail()->addCustomArg($key, (string) $value);
0 ignored issues
show
Bug introduced by
The method addCustomArg does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
254
            }
255
        }
256
    }
257
258
    /**
259
     * @return SendGrid
260
     * @throws Swift_TransportException
261
     */
262
    protected function createApi()
263
    {
264
        if ($this->getApiKey() === null) {
265
            throw new Swift_TransportException('Cannot create instance of \Mandrill while API key is NULL');
266
        }
267
268
269
        $sg = new SendGrid($this->getApiKey());
270
271
        return $sg;
272
    }
273
274
    /**
275
     * @return null|string
276
     */
277
    public function getApiKey()
278
    {
279
        return $this->apiKey;
280
    }
281
282
    /**
283
     * @param string $apiKey
284
     * @return $this
285
     */
286 1
    public function setApiKey($apiKey)
287
    {
288 1
        $this->apiKey = $apiKey;
289
290 1
        return $this;
291
    }
292
}
293