Completed
Push — master ( 078292...b58d46 )
by Jonathan
12s
created

RawMailContext::matchesMail()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 3
eloc 6
nc 3
nop 2
1
<?php
2
3
namespace Drupal\DrupalExtension\Context;
4
5
use Drupal\DrupalMailManager;
6
7
/**
8
 * Provides helper methods for interacting with mail.
9
 */
10
class RawMailContext extends RawDrupalContext
11
{
12
13
  /**
14
   * The mail manager.
15
   *
16
   * @var \Drupal\DrupalMailManagerInterface
17
   */
18
    protected $mailManager;
19
20
  /**
21
   * The number of mails received so far in this scenario, for each mail store.
22
   *
23
   * @var array
24
   */
25
    protected $mailCount = [];
26
27
  /**
28
   * Get the mail manager service that handles stored test mail.
29
   *
30
   * @return \Drupal\DrupalMailManagerInterface
31
   *   The mail manager service.
32
   */
33
    protected function getMailManager()
34
    {
35
        // Persist the mail manager between invocations. This is necessary for
36
        // remembering and reinstating the original mail backend.
37
        if (is_null($this->mailManager)) {
38
            $this->mailManager = new DrupalMailManager($this->getDriver());
39
        }
40
        return $this->mailManager;
41
    }
42
43
  /**
44
   * Get collected mail, matching certain specifications.
45
   *
46
   * @param array $matches
47
   *   Associative array of mail fields and the values to filter by.
48
   * @param bool $new
49
   *   Whether to ignore previously seen mail.
50
   * @param null|int $index
51
   *   A particular mail to return, e.g. 0 for first or -1 for last.
52
   * @param string $store
53
   *   The name of the mail store to get mail from.
54
   *
55
   * @return \stdClass[]
56
   *   An array of mail, each formatted as a Drupal 8
57
   * \Drupal\Core\Mail\MailInterface::mail $message array.
58
   */
59
    protected function getMail($matches = [], $new = false, $index = null, $store = 'default')
60
    {
61
        $mail = $this->getMailManager()->getMail($store);
62
        $previousMailCount = $this->getMailCount($store);
63
        $this->mailCount[$store] = count($mail);
64
65
        // Ignore previously seen mail.
66
        if ($new) {
67
            $mail = array_slice($mail, $previousMailCount);
68
        }
69
70
        // Filter mail based on $matches; keep only mail where each field mentioned
71
        // in $matches contains the value specified for that field.
72
        $mail = array_values(array_filter($mail, function ($singleMail) use ($matches) {
73
            return ($this->matchesMail($singleMail, $matches));
74
        }));
75
76
        // Return an individual mail if specified by an index.
77
        if (is_null($index) || count($mail) === 0) {
78
            return $mail;
79
        } else {
80
            return array_slice($mail, $index, 1)[0];
81
        }
82
    }
83
84
  /**
85
   * Get the number of mails received in a particular mail store.
86
   *
87
   * @return int
88
   *   The number of mails received during this scenario.
89
   */
90
    protected function getMailCount($store)
91
    {
92
        if (array_key_exists($store, $this->mailCount)) {
93
            $count = $this->mailCount[$store];
94
        } else {
95
            $count = 0;
96
        }
97
        return $count;
98
    }
99
100
  /**
101
   * Determine if a mail meets criteria.
102
   *
103
   * @param array $mail
104
   *   The mail, as an array of mail fields.
105
   * @param array $matches
106
   *   The criteria: an associative array of mail fields and desired values.
107
   *
108
   * @return bool
109
   *   Whether the mail matches the criteria.
110
   */
111
    protected function matchesMail($mail = [], $matches = [])
112
    {
113
        // Discard criteria that are just zero-length strings.
114
        $matches = array_filter($matches, 'strlen');
115
        // For each criteria, check the specified mail field contains the value.
116
        foreach ($matches as $field => $value) {
117
            // Case insensitive.
118
            if (stripos($mail[$field], $value) === false) {
119
                return false;
120
            }
121
        }
122
        return true;
123
    }
124
125
  /**
126
   * Compare actual mail with expected mail.
127
   *
128
   * @param array $actualMail
129
   *   An array of actual mail.
130
   * @param array $expectedMail
131
   *   An array of expected mail.
132
   */
133
    protected function compareMail($actualMail, $expectedMail)
134
    {
135
        // Make sure there is the same number of actual and expected.
136
        $expectedCount = count($expectedMail);
137
        $this->assertMailCount($actualMail, $expectedCount);
138
139
        // For each row of expected mail, check the corresponding actual mail.
140
        // Make the comparison insensitive to the order mails were sent.
141
        $actualMail = $this->sortMail($actualMail);
142
        $expectedMail = $this->sortMail($expectedMail);
143
        foreach ($expectedMail as $index => $expectedMailItem) {
144
            // For each column of the expected, check the field of the actual mail.
145
            foreach ($expectedMailItem as $fieldName => $fieldValue) {
146
                $expectedField = [$fieldName => $fieldValue];
147
                $match = $this->matchesMail($actualMail[$index], $expectedField);
148
                if (!$match) {
149
                    throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n'%s'", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName], 0, 30, "...")));
150
                }
151
            }
152
        }
153
    }
154
155
  /**
156
   * Assert there is the expected number of mails, or that there are some mails
157
   * if the exact number expected is not specified.
158
   *
159
   * @param array $actualMail
160
   *   An array of actual mail.
161
   * @param int $expectedCount
162
   *   Optional. The number of mails expected.
163
   */
164
    protected function assertMailCount($actualMail, $expectedCount = null)
165
    {
166
        $actualCount = count($actualMail);
167
        if (is_null($expectedCount)) {
168
            // If number to expect is not specified, expect more than zero.
169
            if ($actualCount === 0) {
170
                throw new \Exception("Expected some mail, but none found.");
171
            }
172
        } else {
173
            if ($expectedCount != $actualCount) {
174
                // Prepare a simple list of actual mail.
175
                $prettyActualMail = [];
176
                foreach ($actualMail as $singleActualMail) {
177
                    $prettyActualMail[] = [
178
                    'to' => $singleActualMail['to'],
179
                    'subject' => $singleActualMail['subject'],
180
                    ];
181
                }
182
                throw new \Exception(sprintf("Expected %s mail, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, true)));
183
            }
184
        }
185
    }
186
  
187
  /**
188
   * Sort mail by to, subject and body.
189
   *
190
   * @param array $mail
191
   *   An array of mail to sort.
192
   *
193
   * @return array
194
   *   The same mail, but sorted.
195
   */
196
    protected function sortMail($mail)
197
    {
198
        // Can't sort an empty array.
199
        if (count($mail) === 0) {
200
            return [];
201
        }
202
203
        // To, subject and body keys must be present.
204
        // Empty strings are ignored when matching so adding them is harmless.
205
        foreach ($mail as $key => $row) {
206
            if (!array_key_exists('to', $row)) {
207
                $mail[$key]['to'] = '';
208
            }
209
            if (!array_key_exists('subject', $row)) {
210
                $mail[$key]['subject'] = '';
211
            }
212
            if (!array_key_exists('body', $row)) {
213
                $mail[$key]['body'] = '';
214
            }
215
        }
216
217
        // Obtain a list of columns.
218
        foreach ($mail as $key => $row) {
219
            if (array_key_exists('to', $row)) {
220
                $to[$key] = $row['to'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$to was never initialized. Although not strictly required by PHP, it is generally a good practice to add $to = 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...
221
            }
222
            if (array_key_exists('subject', $row)) {
223
                $subject[$key] = $row['subject'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$subject was never initialized. Although not strictly required by PHP, it is generally a good practice to add $subject = 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...
224
            }
225
            if (array_key_exists('body', $row)) {
226
                $body[$key] = $row['body'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$body was never initialized. Although not strictly required by PHP, it is generally a good practice to add $body = 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...
227
            }
228
        }
229
230
        // Add $mail as the last parameter, to sort by the common key.
231
        array_multisort($to, SORT_ASC, $subject, SORT_ASC, $body, SORT_ASC, $mail);
0 ignored issues
show
Bug introduced by
The variable $subject does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $body does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
232
        return $mail;
233
    }
234
235
  /**
236
   * Get the mink context, so we can visit pages using the mink session.
237
   */
238
    protected function getMinkContext()
239
    {
240
        $minkContext =  $this->getContext('\Behat\MinkExtension\Context\RawMinkContext');
241
        if ($minkContext === false) {
242
            throw new \Exception(sprintf('No mink context found.'));
243
        }
244
        return $minkContext;
245
    }
246
}
247