Completed
Push — master ( 42b3e6...c7cf7f )
by Zaahid
02:29
created

AbstractConsumer::getAllConsumers()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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