Passed
Pull Request — master (#45)
by Brian
08:50
created

JiraProject::setJiraError()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 2
dl 0
loc 12
ccs 7
cts 7
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * JiraProjectReader
6
 */
7
8
namespace Fr3nch13\Jira\Lib;
9
10
use Cake\Core\Configure;
11
use Fr3nch13\Jira\Exception\IssueSubmissionException;
12
use Fr3nch13\Jira\Exception\MissingAllowedTypeException;
13
use Fr3nch13\Jira\Exception\MissingConfigException;
14
use Fr3nch13\Jira\Exception\MissingDataException;
15
use Fr3nch13\Jira\Exception\MissingIssueException;
16
use Fr3nch13\Jira\Exception\MissingIssueFieldException;
17
use Fr3nch13\Jira\Exception\MissingProjectException;
18
use JiraRestApi\Configuration\ArrayConfiguration;
19
use JiraRestApi\Issue\Issue;
20
use JiraRestApi\Issue\IssueField;
21
use JiraRestApi\Issue\IssueService;
22
use JiraRestApi\Issue\JqlQuery;
23
use JiraRestApi\JiraException;
24
use JiraRestApi\Project\ProjectService;
25
26
/**
27
 * Jira Project class
28
 */
29
class JiraProject
30
{
31
    /**
32
     * @var \JiraRestApi\Configuration\ArrayConfiguration Config Object.
33
     */
34
    public $ConfigObj;
35
36
    /**
37
     * @var string|null The key for the project.
38
     */
39
    public $projectKey = null;
40
41
    /**
42
     * @var \JiraRestApi\Project\ProjectService The project service object.
43
     */
44
    public $ProjectService;
45
46
    /**
47
     * @var \JiraRestApi\Project\Project The project object.
48
     */
49
    protected $Project;
50
51
    /**
52
     * @var array<\JiraRestApi\Issue\Version> The list of a Project's Versions.
53
     */
54
    protected $Versions;
55
56
    /**
57
     * @var \JiraRestApi\Issue\IssueService The project service object.
58
     */
59
    public $IssueService;
60
61
    /**
62
     * @var array<string, mixed> The Cached list of issues.
63
     */
64
    protected $Issues = [];
65
66
    /**
67
     * @var array<string, mixed> The cached list of returned issue info from the below getIssue() method.
68
     */
69
    protected $issuesCache = [];
70
71
    /**
72
     * Valid Types.
73
     * Used to ensure we're getting a valid type when filtering.
74
     * Currently only support Jira Core and Software.
75
     *
76
     * @see https://confluence.atlassian.com/adminjiracloud/issue-types-844500742.html
77
     * @var array<int, string>
78
     */
79
    protected $validTypes = [
80
        'Bug',
81
        'Epic',
82
        'Story',
83
        'Subtask',
84
        'Task',
85
    ];
86
87
    /**
88
     * @var array<string, array<mixed>> Types of issues allowed to be submitted.
89
     */
90
    protected $allowedTypes = [
91
        'Task' => [
92
            'jiraType' => 'Task', // Must be one of the types in the $this->validTypes.
93
            'jiraLabels' => 'task-submitted', // The label used to tag user submitted bugs.
94
            // The form's field information.
95
            'formData' => [
96
                'fields' => [
97
                    'summary' => [
98
                        'type' => 'text',
99
                        'required' => true,
100
                    ],
101
                    'details' => [
102
                        'type' => 'textarea',
103
                        'required' => true,
104
                    ],
105
                ],
106
            ],
107
        ],
108
        'Bug' => [
109
            'jiraType' => 'Bug', // Must be one of the types in the $this->validTypes.
110
            'jiraLabels' => 'bug-submitted', // The label used to tag user submitted bugs.
111
            // The form's field information.
112
            'formData' => [
113
                'fields' => [
114
                    'summary' => [
115
                        'type' => 'text',
116
                        'required' => true,
117
                    ],
118
                    'details' => [
119
                        'type' => 'textarea',
120
                        'required' => true,
121
                    ],
122
                ],
123
            ],
124
        ],
125
        'FeatureRequest' => [
126
            'jiraType' => 'Story', // Must be one of the types in the $this->validTypes.
127
            'jiraLabels' => 'feature-request', // The label used to tag feature requests.
128
            // The form's field information.
129
            'formData' => [
130
                'fields' => [
131
                    'summary' => [
132
                        'type' => 'text',
133
                        'required' => true,
134
                    ],
135
                    'details' => [
136
                        'type' => 'textarea',
137
                        'required' => true,
138
                    ],
139
                ],
140
            ],
141
        ],
142
    ];
143
144
    /**
145
     * This is here for the Form object (or any other object) to use.
146
     * It tacks all errors, even if an exception is thrown.
147
     *
148
     * @var array<int|string, string>
149
     */
150
    protected $errors = [];
151
152
    /**
153
     * Constructor
154
     *
155
     * Reads the configuration, and crdate a config object to be passed to the other objects.
156
     *
157
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException When the project can't be found.
158
     * @return void
159
     */
160 65
    public function __construct()
161
    {
162 65
        $this->configure();
163
164
        // setup the objects
165 65
        $this->ProjectService = new ProjectService($this->ConfigObj);
166
        try {
167 65
            $this->Project = $this->ProjectService->get($this->projectKey);
168
        } catch (JiraException $e) {
169
            $this->setJiraError($this->projectKey, 'MissingProjectException');
170
            throw new MissingProjectException($this->projectKey);
171
        }
172
173 65
        $this->Versions = (array)$this->ProjectService->getVersions($this->projectKey);
174 65
        $this->IssueService = new IssueService($this->ConfigObj);
175
    }
176
177
    /**
178
     * Configures the object.
179
     * Broken out of construct.
180
     *
181
     * @throws \Fr3nch13\Jira\Exception\MissingConfigException When a config setting isn't set.
182
     * @return void
183
     */
184 65
    public function configure(): void
185
    {
186 65
        $schema = Configure::read('Jira.schema');
187 65
        if (!$schema) {
188 1
            $this->setJiraError('schema', 'MissingConfigException');
189 1
            throw new MissingConfigException('schema');
190
        }
191 65
        $host = Configure::read('Jira.host');
192 65
        if (!$host) {
193 1
            $this->setJiraError('host', 'MissingConfigException');
194 1
            throw new MissingConfigException('host');
195
        }
196 65
        $username = Configure::read('Jira.username');
197 65
        if (!$username) {
198 1
            $this->setJiraError('username', 'MissingConfigException');
199 1
            throw new MissingConfigException('username');
200
        }
201 65
        $apiKey = Configure::read('Jira.apiKey');
202 65
        if (!$apiKey) {
203 1
            $this->setJiraError('apiKey', 'MissingConfigException');
204 1
            throw new MissingConfigException('apiKey');
205
        }
206 65
        $projectKey = Configure::read('Jira.projectKey');
207 65
        if (!$projectKey) {
208 1
            $this->setJiraError('projectKey', 'MissingConfigException');
209 1
            throw new MissingConfigException('projectKey');
210
        }
211 65
        $useV3RestApi = Configure::read('Jira.useV3RestApi');
212 65
        if (!$useV3RestApi) {
213 1
            $this->setJiraError('useV3RestApi', 'MissingConfigException');
214 1
            throw new MissingConfigException('useV3RestApi');
215
        }
216 65
        $jiraLogFile = Configure::read('Jira.jiraLogFile');
217 65
        if (!$jiraLogFile) {
218 1
            $this->setJiraError('jiraLogFile', 'MissingConfigException');
219 1
            throw new MissingConfigException('jiraLogFile');
220
        }
221 65
        $this->ConfigObj = new ArrayConfiguration([
222
            'jiraHost' => $schema . '://' . $host,
223
            'jiraUser' => $username,
224
            'jiraPassword' => $apiKey,
225
            'useV3RestApi' => $useV3RestApi,
226
            'jiraLogFile' => $jiraLogFile,
227
        ]);
228
229 65
        $this->projectKey = $projectKey;
230
    }
231
232
    /**
233
     * Get the Project's Info.
234
     *
235
     * @return \JiraRestApi\Project\Project The information about the project.
236
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException If the project can't be found.
237
     */
238 9
    public function getInfo(): \JiraRestApi\Project\Project
239
    {
240 9
        return $this->Project;
241
    }
242
243
    /**
244
     * Get the Project's Versions.
245
     *
246
     * @return array<\JiraRestApi\Issue\Version> A list of version objects.
247
     */
248 2
    public function getVersions(): array
249
    {
250 2
        return $this->Versions;
251
    }
252
253
    /**
254
     * Get the Project's Issues.
255
     *
256
     * @param string|null $type Filter the Issues by type.
257
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
258
     */
259 4
    public function getIssues(?string $type = null): \JiraRestApi\Issue\IssueSearchResult
260
    {
261 4
        $cacheKey = 'all';
262 4
        if ($type) {
263 2
            $cacheKey .= '-' . $type;
264
        }
265 4
        if (!isset($this->Issues[$cacheKey])) {
266 4
            $jql = new JqlQuery();
267
268 4
            $jql->setProject($this->projectKey);
269 4
            if ($type && in_array($type, $this->validTypes)) {
270 2
                $jql->setType($type);
271
            }
272 4
            $jql->addAnyExpression('ORDER BY key DESC');
273
274 4
            $this->Issues[$cacheKey] = $this->IssueService->search($jql->getQuery(), 0, 1000);
275
        }
276
277 4
        return $this->Issues[$cacheKey];
278
    }
279
280
    /**
281
     * Get the Project's Open Issues.
282
     *
283
     * @param string|null $type Filter the Issues by type.
284
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
285
     */
286 4
    public function getOpenIssues(?string $type = null): \JiraRestApi\Issue\IssueSearchResult
287
    {
288 4
        $cacheKey = 'open';
289 4
        if ($type) {
290 2
            $cacheKey .= '-' . $type;
291
        }
292 4
        if (!isset($this->Issues[$cacheKey])) {
293 4
            $jql = new JqlQuery();
294
295 4
            $jql->setProject($this->projectKey);
296 4
            if ($type && in_array($type, $this->validTypes)) {
297 2
                $jql->setType($type);
298
            }
299 4
            $jql->addAnyExpression('AND resolution is EMPTY');
300 4
            $jql->addAnyExpression('ORDER BY key DESC');
301
302 4
            $this->Issues[$cacheKey] = $this->IssueService->search($jql->getQuery(), 0, 1000);
303
        }
304
305 4
        return $this->Issues[$cacheKey];
306
    }
307
308
    /**
309
     * Gets info on a particular issue within your project.
310
     *
311
     * @param int|null $id The issue id. The integer part without the project key.
312
     * @throws \Fr3nch13\Jira\Exception\MissingDataException If the issue's id isn't given.
313
     * @throws \Fr3nch13\Jira\Exception\MissingIssueException If the project's issue can't be found.
314
     * @return \JiraRestApi\Issue\Issue|\JiraRestApi\Issue\IssueV3 the object that has the info of that issue.
315
     */
316 4
    public function getIssue(?int $id = null): \JiraRestApi\Issue\Issue
317
    {
318 4
        if (!is_int($id)) {
319 1
            $this->setJiraError(__('Missing the Issue\'s ID.'), 'Exception');
320 1
            throw new MissingDataException(__('Missing the Issue\'s ID.'));
321
        }
322 3
        $key = $this->projectKey . '-' . $id;
323 3
        if (!isset($this->issuesCache[$key])) {
324
            try {
325 3
                $this->issuesCache[$key] = $this->IssueService->get($key);
326 1
            } catch (JiraException $e) {
327 1
                $this->setJiraError($this->projectKey, 'MissingIssueException');
328 1
                throw new MissingIssueException($key);
329
            }
330
        }
331
332 2
        return $this->issuesCache[$key];
333
    }
334
335
    /**
336
     * Gets a list of issues that are considered bugs.
337
     *
338
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
339
     */
340 2
    public function getBugs(): \JiraRestApi\Issue\IssueSearchResult
341
    {
342 2
        return $this->getIssues('Bug');
343
    }
344
345
    /**
346
     * Gets a list of open issues that are considered bugs.
347
     *
348
     * @return \JiraRestApi\Issue\IssueSearchResult|\JiraRestApi\Issue\IssueSearchResultV3 A list of issue objects.
349
     */
350 2
    public function getOpenBugs(): \JiraRestApi\Issue\IssueSearchResult
351
    {
352 2
        return $this->getOpenIssues('Bug');
353
    }
354
355
    /**
356
     * Methods used to submit an Issue to Jira.
357
     */
358
359
    /**
360
     * Returns the allowed types and their settings
361
     *
362
     * @param string|null $type The type of issue you want to get.
363
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If a type is given, and that type is not configured.
364
     * @return array<string, mixed> the content of $this->allowedTypes.
365
     */
366 29
    public function getAllowedTypes(?string $type = null): array
367
    {
368 29
        if ($type) {
369 8
            if (!isset($this->allowedTypes[$type])) {
370 1
                $this->setJiraError($type, 'MissingAllowedTypeException');
371 1
                throw new MissingAllowedTypeException($type);
372
            }
373
374 7
            return $this->allowedTypes[$type];
375
        }
376
377 26
        return $this->allowedTypes;
378
    }
379
380
    /**
381
     * Allows you to modify the form allowdTypes to fir your situation.
382
     *
383
     * @param string $type The type of issue you want to add/modify.
384
     * @param array<string, mixed> $settings The settings for the type.
385
     * @throws \Fr3nch13\Jira\Exception\MissingIssueFieldException If we're adding a new issue type, and the summary field isn't defined.
386
     * @return void
387
     */
388 16
    public function modifyAllowedTypes(string $type, array $settings = []): void
389
    {
390 16
        if (!isset($this->allowedTypes[$type])) {
391 16
            $this->allowedTypes[$type] = [];
392 16
            if (!isset($settings['jiraType'])) {
393 1
                $this->setJiraError('jiraType', 'MissingIssueFieldException');
394 1
                throw new MissingIssueFieldException('jiraType');
395
            }
396 15
            if (!isset($settings['formData'])) {
397 1
                $this->setJiraError('formData', 'MissingIssueFieldException');
398 1
                throw new MissingIssueFieldException('formData');
399
            }
400 14
            if (!isset($settings['formData']['fields'])) {
401 1
                $this->setJiraError('formData.fields', 'MissingIssueFieldException');
402 1
                throw new MissingIssueFieldException('formData.fields');
403
            }
404 13
            if (!isset($settings['formData']['fields']['summary'])) {
405 1
                $this->setJiraError('formData.fields.summary', 'MissingIssueFieldException');
406 1
                throw new MissingIssueFieldException('formData.fields.summary');
407
            }
408
        }
409
410 12
        $this->allowedTypes[$type] += $settings;
411
    }
412
413
    /**
414
     * Checks to see if a type is allowed.
415
     *
416
     * @param string $type The type to check.
417
     * @return bool if it's allowed or not.
418
     */
419 32
    public function isAllowedType(string $type): bool
420
    {
421 32
        return isset($this->allowedTypes[$type]) ? true : false;
422
    }
423
424
    /**
425
     * Gets the array for the forms when submitting an issue to Jira.
426
     *
427
     * @param string|null $type The type of issue we're submitting.
428
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that type is not configured.
429
     * @throws \Fr3nch13\Jira\Exception\MissingDataException If the form data for that type is missing.
430
     * @return array<string, mixed> The array of data to fill in the form with.
431
     */
432 27
    public function getFormData(?string $type = null): array
433
    {
434 27
        if (!$type) {
435 1
            $this->setJiraError('[$type is not set]', 'MissingAllowedTypeException');
436 1
            throw new MissingAllowedTypeException('[$type is not set]');
437
        }
438
439 26
        if (!$this->isAllowedType($type)) {
440 1
            $this->setJiraError($type, 'MissingAllowedTypeException');
441 1
            throw new MissingAllowedTypeException($type);
442
        }
443
444 25
        $allowedTypes = $this->getAllowedTypes();
445
446 25
        if (!isset($allowedTypes[$type]['formData'])) {
447
            $this->setJiraError('No form data is set.', 'MissingDataException');
448
            throw new MissingDataException(__('No form data is set.'));
449
        }
450
451 25
        if (!isset($allowedTypes[$type]['formData']['fields'])) {
452
            $this->setJiraError('No form data fields are set.', 'MissingDataException');
453
            throw new MissingDataException(__('No form data fields are set.'));
454
        }
455
456 25
        return $allowedTypes[$type]['formData'];
457
    }
458
459
    /**
460
     * Sets the formData variable if you want to modify the default/initial values.
461
     *
462
     * @param string $type The type you want to set the data for.
463
     *  - Needs to be in the allowedTypes already.
464
     * @param array<string, mixed> $data The definition of the allowed types
465
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that type is not configured.
466
     * @throws \Fr3nch13\Jira\Exception\MissingDataException Uf the fields aren't defined.
467
     * @return void
468
     */
469 26
    public function setFormData(string $type, array $data = []): void
470
    {
471 26
        if (!$this->isAllowedType($type)) {
472 1
            $this->setJiraError($type, 'MissingAllowedTypeException');
473 1
            throw new MissingAllowedTypeException($type);
474
        }
475
476 25
        if (!isset($data['fields'])) {
477 1
            $this->setJiraError('No form data fields are set.', 'MissingDataException');
478 1
            throw new MissingDataException(__('No form data fields are set.'));
479
        }
480
481 24
        $this->allowedTypes[$type]['formData'] = $data;
482
    }
483
484
    /**
485
     * Submits the Issue
486
     *
487
     * @param string $type The type you want to set the data for.
488
     *  - Needs to be in the allowedTypes already.
489
     * @param array<string, mixed> $data The array of details about the issue.
490
     * @throws \Fr3nch13\Jira\Exception\IssueSubmissionException If submitting the issue fails.
491
     * @throws \Fr3nch13\Jira\Exception\MissingAllowedTypeException If that issue type is not configured.
492
     * @throws \Fr3nch13\Jira\Exception\MissingIssueFieldException If we're adding a new issue, and required fields aren't defined.
493
     * @return int > 0 If the request was successfully submitted.
494
     */
495 8
    public function submitIssue(string $type, array $data = []): int
496
    {
497 8
        if (!$this->isAllowedType($type)) {
498 1
            $this->setJiraError($type, 'MissingAllowedTypeException');
499 1
            throw new MissingAllowedTypeException($type);
500
        }
501
502 7
        if (!isset($data['summary'])) {
503 1
            $this->setJiraError('summary', 'MissingIssueFieldException');
504 1
            throw new MissingIssueFieldException('summary');
505
        }
506
507 6
        $issueField = $this->buildSubmittedIssue($type, $data);
508
509 6
        $issueService = new IssueService($this->ConfigObj);
510
511
        try {
512 6
            $ret = $issueService->create($issueField);
513
        } catch (JiraException $e) {
514
            //Sample return error with json in it.
515
            //Pasting here so I can mock this return message in the unit tests.
516
            //CURL HTTP Request Failed: Status Code : 400, URL:https://[hostname]/rest/api/2/issue
517
            //Error Message : {"errorMessages":[],"errors":{"user_type":"Field 'user_type' cannot be set. It is not on the appropriate screen, or unknown."}}             */
518
            $msg = $e->getMessage();
519
            if (strpos($msg, '{') !== false) {
520
                $msgArray = str_split($msg);
521
                // extract the json message.
522
                $json = '';
523
                $in = 0;
524
                foreach ($msgArray as $i => $char) {
525
                    if ($char == '{') {
526
                        $in++;
527
                    }
528
                    if ($in) {
529
                        $json .= $msg[$i];
530
                    }
531
                    if ($char == '}') {
532
                        $in--;
533
                    }
534
                }
535
                if ($json) {
536
                    $json = json_decode($json, true);
537
                }
538
                if ($json) {
539
                    $newMsg = [];
540
                    if (isset($json['errorMessages'])) {
541
                        foreach ($json['errorMessages'] as $jsonMsg) {
542
                            $newMsg[] = $jsonMsg;
543
                        }
544
                        foreach ($json['errors'] as $jsonMsg) {
545
                            $newMsg[] = $jsonMsg;
546
                        }
547
                        $msg = implode("\n", $newMsg);
548
                    }
549
                }
550
            }
551
            $this->setJiraError($msg, 'IssueSubmissionException');
552
            throw new IssueSubmissionException($msg);
553
        }
554
555 6
        if ($ret instanceof Issue && $ret->id) {
556 6
            return (int)$ret->id;
557
        }
558
559
        return 0;
560
    }
561
562
    /**
563
     * Creates the issue to send to the server.
564
     *
565
     * @param string $type The type of isse we're creating.
566
     * @param array<string, mixed> $data The data from the submitted form.
567
     * @throws \Fr3nch13\Jira\Exception\MissingProjectException If submitting the issue fails.
568
     * @return \JiraRestApi\Issue\IssueField
569
     */
570 7
    public function buildSubmittedIssue(string $type, array $data = []): \JiraRestApi\Issue\IssueField
571
    {
572 7
        $typeInfo = $this->getAllowedTypes($type);
573
574
        // make sure we can get the project info first.
575
        // getInfo will throw an exception if it can't find the project.
576
        // putting a try/catch around it so scrutinizer stops complaining.
577
        try {
578 7
            $project = $this->getInfo();
579
        } catch (MissingProjectException $e) {
580
            $this->setJiraError($this->projectKey, 'MissingProjectException');
581
            throw $e;
582
        }
583
584 7
        $issueField = new IssueField();
585 7
        $issueField->setProjectKey($this->projectKey)
586 7
            ->setIssueType($typeInfo['jiraType']);
587
588 7
        if (isset($data['summary'])) {
589 7
            $issueField->setSummary($data['summary']);
590
        }
591 7
        if (isset($data['description'])) {
592 2
            $issueField->setDescription($data['description']);
593
        }
594 7
        if (isset($data['priority'])) {
595 1
            $issueField->setPriorityName($data['priority']);
596
        }
597 7
        if (isset($data['assignee'])) {
598 1
            $issueField->setAssigneeName($data['assignee']);
599
        }
600 7
        if (isset($data['version'])) {
601 1
            $issueField->addVersion($data['version']);
602
        }
603 7
        if (isset($data['components'])) {
604 1
            $issueField->addComponents($data['components']);
605
        }
606 7
        if (isset($data['duedate'])) {
607 1
            $issueField->setDueDate($data['duedate']);
608
        }
609
610
        // labels should be space seperated
611 7
        if (isset($typeInfo['jiraLabels'])) {
612 7
            if (is_string($typeInfo['jiraLabels'])) {
613 7
                $typeInfo['jiraLabels'] = preg_split('/\s+/', $typeInfo['jiraLabels']);
614
            }
615
            // track the type with a label
616 7
            $typeInfo['jiraLabels'][] = 'user-submitted-type-' . $type;
617 7
            foreach ($typeInfo['jiraLabels'] as $jiralabel) {
618 7
                $issueField->addLabel($jiralabel);
619
            }
620
        }
621
622 7
        return $issueField;
623
    }
624
625
    /**
626
     * Sets an error
627
     *
628
     * @param string $msg The error message.
629
     * @param string $key The key to use in the this->errors array.
630
     * @return bool If saved or not.
631
     */
632 21
    public function setJiraError(string $msg = '', string $key = ''): bool
633
    {
634 21
        if (!trim($msg)) {
635 1
            return false;
636
        }
637 21
        if ($key) {
638 21
            $this->errors[$key] = $msg;
639
        } else {
640 1
            $this->errors[] = $msg;
641
        }
642
643 21
        return true;
644
    }
645
646
    /**
647
     * Gets the accumulated error messages.
648
     * If a key is given, return that specific message. If that key doesn't exist, return false.
649
     *
650
     * @return array<int|string, string>
651
     */
652 1
    public function getJiraErrors(): array
653
    {
654 1
        return $this->errors;
655
    }
656
}
657