Passed
Push — master ( 19aa6f...a676bf )
by Sebastian
03:24
created

src/Mailcode/Collection.php (1 issue)

1
<?php
2
/**
3
 * File containing the {@see Mailcode_Collection} class.
4
 *
5
 * @package Mailcode
6
 * @subpackage Collection
7
 * @see Mailcode_Collection
8
 */
9
10
declare(strict_types=1);
11
12
namespace Mailcode;
13
14
use AppUtils\OperationResult;
15
16
/**
17
 * Commands collection: container for commands.
18
 *
19
 * @package Mailcode
20
 * @subpackage Collection
21
 * @author Sebastian Mordziol <[email protected]>
22
 */
23
class Mailcode_Collection
24
{
25
    const ERROR_CANNOT_RETRIEVE_FIRST_ERROR = 52301;
26
    const ERROR_CANNOT_MODIFY_FINALIZED = 52302;
27
    
28
   /**
29
    * @var Mailcode_Commands_Command[]
30
    */
31
    protected $commands = array();
32
    
33
    /**
34
     * @var Mailcode_Collection_Error[]
35
     */
36
    protected $errors = array();
37
    
38
   /**
39
    * @var OperationResult|NULL
40
    */
41
    protected $validationResult;
42
43
    /**
44
     * @var bool
45
     */
46
    private $finalized = false;
47
48
    /**
49
    * Adds a command to the collection.
50
    * 
51
    * @param Mailcode_Commands_Command $command
52
    * @return Mailcode_Collection
53
    */
54
    public function addCommand(Mailcode_Commands_Command $command) : Mailcode_Collection
55
    {
56
        if($this->finalized)
57
        {
58
            throw new Mailcode_Exception(
59
                'Cannot add commands to a finalized collection',
60
                'When a collection has been finalized, it may not be modified anymore.',
61
                self::ERROR_CANNOT_MODIFY_FINALIZED
62
            );
63
        }
64
65
        $this->commands[] = $command;
66
67
        // reset the collection's validation result, since it
68
        // depends on the commands.
69
        $this->validationResult = null;
70
        
71
        return $this;
72
    }
73
    
74
   /**
75
    * Whether there are any commands in the collection.
76
    * 
77
    * @return bool
78
    */
79
    public function hasCommands() : bool
80
    {
81
        return !empty($this->commands);
82
    }
83
    
84
   /**
85
    * Counts the amount of commands in the collection.
86
    * 
87
    * @return int
88
    */
89
    public function countCommands() : int
90
    {
91
        return count($this->commands);
92
    }
93
94
    public function addErrorMessage(string $matchedText, string $message, int $code) : void
95
    {
96
        $this->errors[] = new Mailcode_Collection_Error_Message(
97
            $matchedText,
98
            $code,
99
            $message
100
        );
101
    }
102
    
103
    public function addInvalidCommand(Mailcode_Commands_Command $command) : void
104
    {
105
        // Remove the command in case it was already added
106
        $this->removeCommand($command);
107
108
        $this->errors[] = new Mailcode_Collection_Error_Command($command);
109
    }
110
111
    public function removeCommand(Mailcode_Commands_Command $command) : void
112
    {
113
        $keep = array();
114
115
        foreach($this->commands as $existing)
116
        {
117
            if($existing !== $command)
118
            {
119
                $keep[] = $existing;
120
            }
121
        }
122
123
        $this->commands = $keep;
124
    }
125
    
126
   /**
127
    * @return Mailcode_Collection_Error[]
128
    */
129
    public function getErrors()
130
    {
131
        $result = $this->getValidationResult();
132
        
133
        $errors = $this->errors;
134
        
135
        if(!$result->isValid())
136
        {
137
            $errors[] = new Mailcode_Collection_Error_Message(
138
                '',
139
                $result->getCode(),
140
                $result->getErrorMessage()
141
            );
142
        }
143
        
144
        return $errors;
145
    }
146
    
147
    public function getFirstError() : Mailcode_Collection_Error
148
    {
149
        $errors = $this->getErrors();
150
        
151
        if(!empty($errors))
152
        {
153
            return array_shift($errors);
154
        }
155
        
156
        throw new Mailcode_Exception(
157
            'Cannot retrieve first error: no errors detected',
158
            null,
159
            self::ERROR_CANNOT_RETRIEVE_FIRST_ERROR
160
        );
161
    }
162
    
163
    public function isValid() : bool
164
    {
165
        $errors = $this->getErrors();
166
        
167
        return empty($errors);
168
    }
169
    
170
   /**
171
    * Retrieves all commands that were detected, in the exact order
172
    * they were found.
173
    * 
174
    * @return \Mailcode\Mailcode_Commands_Command[]
175
    */
176
    public function getCommands()
177
    {
178
       return $this->commands;
179
    }
180
    
181
   /**
182
    * Retrieves all unique commands by their matched
183
    * string hash: this ensures only commands that were
184
    * written the exact same way (including spacing)
185
    * are returned.
186
    * 
187
    * @return \Mailcode\Mailcode_Commands_Command[]
188
    */
189
    public function getGroupedByHash()
190
    {
191
        $hashes = array();
192
        
193
        foreach($this->commands as $command)
194
        {
195
            $hash = $command->getHash();
196
            
197
            if(!isset($hashes[$hash]))
198
            {
199
                $hashes[$hash] = $command;
200
            }
201
        }
202
            
203
        return array_values($hashes);
204
    }
205
206
   /**
207
    * Adds several commands at once.
208
    * 
209
    * @param Mailcode_Commands_Command[] $commands
210
    * @return Mailcode_Collection
211
    */
212
    public function addCommands(array $commands) : Mailcode_Collection
213
    {
214
        foreach($commands as $command)
215
        {
216
            $this->addCommand($command);
217
        }
218
        
219
        return $this;
220
    }
221
    
222
    public function mergeWith(Mailcode_Collection $collection) : Mailcode_Collection
223
    {
224
        $merged = new Mailcode_Collection();
225
        $merged->addCommands($this->getCommands());
226
        $merged->addCommands($collection->getCommands());
227
228
        return $merged;
229
    }
230
    
231
    public function getVariables() : Mailcode_Variables_Collection
232
    {
233
        $collection = new Mailcode_Variables_Collection_Regular();
234
        
235
        foreach($this->commands as $command)
236
        {
237
            $collection->mergeWith($command->getVariables());
238
        }
239
        
240
        return $collection;
241
    }
242
    
243
    public function getValidationResult() : OperationResult
244
    {
245
        if($this->validationResult instanceof OperationResult)
246
        {
247
            return $this->validationResult;
248
        }
249
        
250
        $nesting = new Mailcode_Collection_NestingValidator($this);
251
        
252
        $this->validationResult = $nesting->validate(); 
253
        
254
        return $this->validationResult;
255
    }
256
    
257
    public function hasErrorCode(int $code) : bool
258
    {
259
        $errors = $this->getErrors();
260
        
261
        foreach($errors as $error)
262
        {
263
            if($error->getCode() === $code)
264
            {
265
                return true;
266
            }
267
        }
268
        
269
        return false;
270
    }
271
272
    /**
273
     * Retrieves only ShowXXX commands in the collection, if any.
274
     * Includes ShowVariable, ShowDate, ShowNumber, ShowSnippet.
275
     *
276
     * @return Mailcode_Commands_ShowBase[]
277
     */
278
    public function getShowCommands(): array
279
    {
280
        return $this->getCommandsByClass(Mailcode_Commands_ShowBase::class);
281
    }
282
283
    /**
284
     * Retrieves all commands that implement the ListVariables interface,
285
     * meaning that they use list variables.
286
     *
287
     * @return Mailcode_Interfaces_Commands_ListVariables[]
288
     * @see Mailcode_Interfaces_Commands_ListVariables
289
     */
290
    public function getListVariableCommands() : array
291
    {
292
        return $this->getCommandsByClass(Mailcode_Interfaces_Commands_ListVariables::class);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getCommand...s_ListVariables::class) returns an array which contains values of type Mailcode\Mailcode_Commands_Command which are incompatible with the documented value type Mailcode\Mailcode_Interf..._Commands_ListVariables.
Loading history...
293
    }
294
295
    /**
296
    * Retrieves only show variable commands in the collection, if any.
297
    * 
298
    * @return Mailcode_Commands_Command_ShowVariable[]
299
    */
300
    public function getShowVariableCommands(): array
301
    {
302
        return $this->getCommandsByClass(Mailcode_Commands_Command_ShowVariable::class);
303
    }
304
305
    /**
306
     * @return Mailcode_Commands_Command_For[]
307
     */
308
    public function getForCommands()
309
    {
310
        return $this->getCommandsByClass(Mailcode_Commands_Command_For::class);
311
    }
312
313
   /**
314
    * Retrieves only show date commands in the collection, if any.
315
    *
316
    * @return Mailcode_Commands_Command_ShowDate[]
317
    */
318
    public function getShowDateCommands() : array
319
    {
320
        return $this->getCommandsByClass(Mailcode_Commands_Command_ShowDate::class);
321
    }
322
323
    /**
324
     * @param string $className
325
     * @return Mailcode_Commands_Command[]
326
     */
327
    public function getCommandsByClass(string $className) : array
328
    {
329
        $result = array();
330
331
        foreach($this->commands as $command)
332
        {
333
            if($command instanceof $className)
334
            {
335
                $result[] = $command;
336
            }
337
        }
338
339
        return $result;
340
    }
341
    
342
    public function getFirstCommand() : ?Mailcode_Commands_Command
343
    {
344
        $commands = $this->getCommands();
345
        
346
        if(!empty($commands))
347
        {
348
            return array_shift($commands);
349
        }
350
        
351
        return null;
352
    }
353
354
    public function finalize() : void
355
    {
356
        $this->finalized = true;
357
358
        $this->validateNesting();
359
    }
360
361
    public function isFinalized() : bool
362
    {
363
        return $this->finalized;
364
    }
365
366
    private function validateNesting() : void
367
    {
368
        foreach($this->commands as $command)
369
        {
370
            $command->validateNesting();
371
372
            if(!$command->isValid()) {
373
                $this->addInvalidCommand($command);
374
            }
375
        }
376
    }
377
}
378