Completed
Push — master ( ab502e...07a33d )
by Zaahid
06:14
created

AbstractConsumer::__invoke()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * This file is part of the ZBateson\MailMimeParser project.
4
 *
5
 * @license http://opensource.org/licenses/bsd-license.php BSD
6
 */
7
namespace ZBateson\MailMimeParser\Header\Consumer;
8
9
use ZBateson\MailMimeParser\Header\Consumer\ConsumerService;
10
use ZBateson\MailMimeParser\Header\Part\HeaderPartFactory;
11
use SplFixedArray;
12
use Iterator;
13
use NoRewindIterator;
14
15
/**
16
 * Abstract base class for all header token consumers.
17
 * 
18
 * Defines the base parser that loops over tokens, consuming them and creating
19
 * header parts.
20
 *
21
 * @author Zaahid Bateson
22
 */
23
abstract class AbstractConsumer
24
{
25
    /**
26
     * @var \ZBateson\MailMimeParser\Header\Consumer\ConsumerService used to
27
     *      get consumer instances for sub-consumers
28
     */
29
    protected $consumerService;
30
    
31
    /**
32
     * @var \ZBateson\MailMimeParser\Header\Part\HeaderPartFactory used to construct
33
     * HeaderPart objects
34
     */
35
    protected $partFactory;
36
    
37
    /**
38
     * Initializes the instance.
39
     * 
40
     * @param ConsumerService $consumerService
41
     * @param HeaderPartFactory $partFactory
42
     */
43 1
    protected function __construct(ConsumerService $consumerService, HeaderPartFactory $partFactory)
44
    {
45 1
        $this->consumerService = $consumerService;
46 1
        $this->partFactory = $partFactory;
47 1
    }
48
    
49
    /**
50
     * Returns the singleton instance for the class.
51
     * 
52
     * @param ConsumerService $consumerService
53
     * @param HeaderPartFactory $partFactory
54
     */
55 31
    public static function getInstance(ConsumerService $consumerService, HeaderPartFactory $partFactory)
56
    {
57 31
        static $instances = [];
58 31
        $class = get_called_class();
59 31
        if (!isset($instances[$class])) {
60 1
            $instances[$class] = new static($consumerService, $partFactory);
61 1
        }
62 31
        return $instances[$class];
63
    }
64
    
65
    /**
66
     * Invokes parsing of a header's value into header parts.
67
     * 
68
     * @param string $value the raw header value
69
     * @return \ZBateson\MailMimeParser\Header\Part\HeaderPart[] the array of parsed
70
     *         parts
71
     */
72 26
    public function __invoke($value)
73
    {
74 26
        if ($value !== '') {
75 25
            return $this->parseRawValue($value);
76
        }
77 1
        return [];
78
    }
79
    
80
    /**
81
     * Called during construction to set up the list of sub-consumers that will
82
     * take control from this consumer should a token match a sub-consumer's
83
     * start token.
84
     * 
85
     * @return AbstractConsumer[] the array of consumers
86
     */
87
    abstract protected function getSubConsumers();
88
    
89
    /**
90
     * Returns this consumer and all unique sub consumers.
91
     * 
92
     * Loops into the sub-consumers (and their sub-consumers, etc...) finding
93
     * all unique consumers, and returns them in an array.
94
     * 
95
     * @return \ZBateson\MailMimeParser\Header\AbstractConsumer[]
96
     */
97 25
    private function getAllConsumers()
98
    {
99 25
        $found = [$this];
100
        do {
101 25
            $current = current($found);
102 25
            $subConsumers = $current->getSubConsumers();
103 25
            foreach ($subConsumers as $consumer) {
104 22
                if (!in_array($consumer, $found)) {
105 22
                    $found[] = $consumer;
106 22
                }
107 25
            }
108 25
        } while (next($found) !== false);
109 25
        return $found;
110
    }
111
    
112
    /**
113
     * Called by __invoke to parse the raw header value into header parts.
114
     * 
115
     * Calls splitTokens to split the value into token part strings, then calls
116
     * parseParts to parse the returned array.
117
     * 
118
     * @param string $value
119
     * @return \ZBateson\MailMimeParser\Header\Part\HeaderPart[] the array of parsed
120
     *         parts
121
     */
122 25
    private function parseRawValue($value)
123
    {
124 25
        $tokens = $this->splitRawValue($value);
125 25
        return $this->parseTokensIntoParts(new NoRewindIterator(SplFixedArray::fromArray($tokens)));
126
    }
127
    
128
    /**
129
     * Returns an array of regular expression separators specific to this
130
     * consumer.  The returned patterns are used to split the header value into
131
     * tokens for the consumer to parse into parts.
132
     * 
133
     * Each array element makes part of a generated regular expression that is
134
     * used in a call to preg_split().  RegEx patterns can be used, and care
135
     * should be taken to escape special characters.
136
     * 
137
     * @return string[] the array of patterns
138
     */
139
    abstract protected function getTokenSeparators();
140
    
141
    /**
142
     * Returns a list of regular expression markers for this consumer and all
143
     * sub-consumers by calling 'getTokenSeparators'..
144
     * 
145
     * @return string[] an array of regular expression markers
146
     */
147 25
    private function getAllTokenSeparators()
148
    {
149 25
        $markers = $this->getTokenSeparators();
150 25
        $subConsumers = $this->getAllConsumers();
151 25
        foreach ($subConsumers as $consumer) {
152 25
            $markers = array_merge($consumer->getTokenSeparators(), $markers);
153 25
        }
154 25
        return array_unique($markers);
155
    }
156
    
157
    /**
158
     * Returns a regex pattern used to split the input header string.  The
159
     * default implementation calls getAllTokenSeparators and implodes the
160
     * returned array with the regex OR '|' character as its glue.
161
     * 
162
     * @return string the regex pattern
163
     */
164 25
    protected function getTokenSplitPattern()
165
    {
166 25
        $sChars = implode('|', $this->getAllTokenSeparators());
167 25
        return '~(\\\\.|' . $sChars . ')~';
168
    }
169
    
170
    /**
171
     * Returns an array of split tokens from the input string.
172
     * 
173
     * The method calls preg_split using getTokenSplitPattern.  The split
174
     * array will not contain any empty parts and will contain the markers.
175
     * 
176
     * @param string $rawValue the raw string
177
     * @return array the array of tokens
178
     */
179 25
    protected function splitRawValue($rawValue)
180
    {
181 25
        return preg_split(
182 25
            $this->getTokenSplitPattern(),
183 25
            $rawValue,
184 25
            -1,
185 25
            PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
186 25
        );
187
    }
188
    
189
    /**
190
     * Returns true if the passed string token marks the beginning marker for
191
     * the current consumer.
192
     * 
193
     * @param string $token the current token
194
     */
195
    abstract protected function isStartToken($token);
196
    
197
    /**
198
     * Returns true if the passed string token marks the end marker for the
199
     * current consumer.
200
     * 
201
     * @param string $token the current token
202
     */
203
    abstract protected function isEndToken($token);
204
    
205
    /**
206
     * Constructs and returns a \ZBateson\MailMimeParser\Header\Part\HeaderPart
207
     * for the passed string token.  If the token should be ignored, the
208
     * function must return null.
209
     * 
210
     * The default created part uses the instance's partFactory->newInstance
211
     * method.
212
     * 
213
     * @param string $token the token
214
     * @param bool $isLiteral set to true if the token represents a literal -
215
     *        e.g. an escaped token
216
     * @return \ZBateson\MailMimeParser\Header\Part\HeaderPart the constructed
217
     *         header part or null if the token should be ignored
218
     */
219 13
    protected function getPartForToken($token, $isLiteral)
220
    {
221 13
        if ($isLiteral) {
222 1
            return $this->partFactory->newLiteralPart($token);
223 13
        } elseif (preg_match('/^\s+$/', $token)) {
224 10
            return $this->partFactory->newToken(' ');
225
        }
226 13
        return $this->partFactory->newInstance($token);
227
    }
228
    
229
    /**
230
     * Iterates through this consumer's sub-consumers checking if the current
231
     * token triggers a sub-consumer's start token and passes control onto that
232
     * sub-consumer's parseTokenIntoParts.  If no sub-consumer is responsible
233
     * for the current token, calls getPartForToken and returns it in an array.
234
     * 
235
     * @param Iterator $tokens
236
     * @return \ZBateson\MailMimeParser\Header\Part\HeaderPart[]|array
237
     */
238 25
    private function getConsumerTokenParts(Iterator $tokens)
239
    {
240 25
        $token = $tokens->current();
241 25
        $subConsumers = $this->getSubConsumers();
242 25
        foreach ($subConsumers as $consumer) {
243 22
            if ($consumer->isStartToken($token)) {
244 13
                $this->advanceToNextToken($tokens, true);
245 13
                return $consumer->parseTokensIntoParts($tokens);
246
            }
247 25
        }
248 25
        return [$this->getPartForToken($token, false)];
249
    }
250
    
251
    /**
252
     * Returns an array of \ZBateson\MailMimeParser\Header\Part\HeaderPart for
253
     * the current token on the iterator.
254
     * 
255
     * If the current token is a start token from a sub-consumer, the sub-
256
     * consumer's parseTokensIntoParts method is called.
257
     * 
258
     * @param Iterator $tokens
259
     * @return \ZBateson\MailMimeParser\Header\Part\HeaderPart[]|array
260
     */
261 25
    protected function getTokenParts(Iterator $tokens)
262
    {
263 25
        $token = $tokens->current();
264 25
        if (strlen($token) === 2 && $token[0] === '\\') {
265 3
            return [$this->getPartForToken(substr($token, 1), true)];
266
        }
267 25
        return $this->getConsumerTokenParts($tokens);
268
    }
269
    
270
    /**
271
     * Determines if the iterator should be advanced to the next token after
272
     * reading tokens or finding a start token.
273
     * 
274
     * The default implementation will advance for a start token, but not
275
     * advance on the end token of the current consumer, allowing the end token
276
     * to be passed up to a higher-level consumer.
277
     * 
278
     * @param Iterator $tokens
279
     * @param bool $isStartToken
280
     */
281 22
    protected function advanceToNextToken(Iterator $tokens, $isStartToken)
282
    {
283 22
        if (($isStartToken) || ($tokens->valid() && !$this->isEndToken($tokens->current()))) {
284 22
            $tokens->next();
285 22
        }
286 22
    }
287
    
288
    /**
289
     * Iterates over the passed token Iterator and returns an array of parsed
290
     * \ZBateson\MailMimeParser\Header\Part\HeaderPart objects.
291
     * 
292
     * The method checks each token to see if the token matches a sub-consumer's
293
     * start token, or if it matches the current consumer's end token to stop
294
     * processing.
295
     * 
296
     * If a sub-consumer's start token is matched, the sub-consumer is invoked
297
     * and its returned parts are merged to the current consumer's header parts.
298
     * 
299
     * After all tokens are read and an array of Header\Parts are constructed,
300
     * the array is passed to AbstractConsumer::processParts for any final
301
     * processing.
302
     * 
303
     * @param Iterator $tokens an iterator over a string of tokens
304
     * @return \ZBateson\MailMimeParser\Header\Part\HeaderPart[] an array of parsed
305
     *         parts
306
     */
307 25
    protected function parseTokensIntoParts(Iterator $tokens)
308
    {
309 25
        $parts = [];
310 25
        while ($tokens->valid() && !$this->isEndToken($tokens->current())) {
311 25
            $parts = array_merge($parts, $this->getTokenParts($tokens));
312 25
            $this->advanceToNextToken($tokens, false);
313 25
        }
314 25
        return $this->processParts($parts);
315
    }
316
    
317
    /**
318
     * Performs any final processing on the array of parsed parts before
319
     * returning it to the consumer client.
320
     * 
321
     * The default implementation simply returns the passed array.
322
     * 
323
     * @param \ZBateson\MailMimeParser\Header\Part\HeaderPart[] $parts
324
     * @return \ZBateson\MailMimeParser\Header\Part\HeaderPart[]
325
     */
326 5
    protected function processParts(array $parts)
327
    {
328 5
        return $parts;
329
    }
330
}
331