Passed
Push — master ( fbe757...79226f )
by George
03:16
created

Lexical   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 272
Duplicated Lines 8.46 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 87.95%

Importance

Changes 5
Bugs 0 Features 1
Metric Value
wmc 25
c 5
b 0
f 1
lcom 1
cbo 3
dl 23
loc 272
ccs 73
cts 83
cp 0.8795
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A checkRowHasExpectedColumnCount() 0 7 1
A handleUnexpectedColumnCount() 0 6 1
A checkMandatoryColumnHasData() 0 8 2
A handleInvalidMandatoryColumn() 7 7 1
A validateSpecificFormat() 0 9 1
A handleInvalidFormat() 8 8 1
A getColumnPattern() 0 7 3
A validatePattern() 0 11 3
A setRowsAnalysedStatistic() 0 4 1
C validate() 0 49 10
A handleInvalidPattern() 8 8 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
namespace JsonTable\Analyse;
3
4
/**
5
 * Perform lexical analysis.
6
 * 
7
 * @package JsonTable
8
 */
9
class Lexical extends Analyse implements AnalyseInterface
10
{
11
    /**
12
     * @var string The description for fields with invalid formats.
13
     */
14
    const ERROR_INVALID_PATTERN = 'There are <strong>%d</strong> fields that don\'t have the correct pattern:';
15
16
    /**
17
     * @var string The description for fields with invalid formats.
18
     */
19
    const ERROR_INVALID_FORMAT = 'There are <strong>%d</strong> fields that don\'t have the correct format:';
20
21
    /**
22
     * @var string The description for rows with missing columns.
23
     */
24
    const ERROR_REQUIRED_FIELD_MISSING_DATA = 'There are <strong>%d</strong> required fields with missing data:';
25
26
    /**
27
     * @var string The description for rows with missing columns.
28
     */
29
    const ERROR_INCORRECT_COLUMN_COUNT = 'There are the wrong number of columns';
30
31
    /**
32
     * @var array   The current CSV row being analysed.
33
     */
34
    private $currentCsvRow;
35
36
    /**
37
     * @var int The position of the CSV column currently being analysed.
38
     */
39
    private $csvColumnPosition;
40
41
    /**
42
     * @var int The position of the current CSV row row in the CSV file.
43
     */
44
    private $rowNumber;
45
46
    /**
47
     * @var object  The schema definition for the column currently being analysed.
48
     */
49
    private $schemaColumn;
50
51
    /**
52
     * @var int The number of columns in the currently analysed row.
53
     */
54
    private $columnCount;
55
56
    /**
57
     * @var int The number of columns expected in each row.
58
     * This is taken from the CSV header row.
59
     */
60
    private $expectedColumnCount;
61
62
    /**
63
     * @var string  The pattern to validate the current field against.
64
     */
65
    private $pattern;
66
67
    /**
68
     * @var string  The format to validate the current field against.
69
     */
70
    private $format;
71
72
    /**
73
     * @var bool    Whether the file is valid.
74
     */
75
    private $valid;
76
77
78
    /**
79
     * Validate that all fields are of the correct type, format and pattern.
80
     * This also checks that each CSV row has the expected number of columns.
81
     *
82
     * @return  boolean Is all data lexically valid.
83
     */
84 71
    public function validate()
85
    {
86 71
        $this->valid = true;
87 71
        $this->rowNumber = 1;
88
89 71
        parent::rewindFilePointerToFirstData();
90
91 71
        while ($currentCsvRow = parent::loopThroughFileRows()) {
92 71
            $this->currentCsvRow = $currentCsvRow;
93
            
94 71
            if (!$this->checkRowHasExpectedColumnCount()) {
95
                $this->handleUnexpectedColumnCount();
96
            }
97
98 71
            for ($this->csvColumnPosition = 0; $this->csvColumnPosition < $this->columnCount; $this->csvColumnPosition++) {
99 71
                $this->schemaColumn = $this->getSchemaColumnFromCsvColumnPosition($this->csvColumnPosition);
100
101 71
                if (!$this->checkMandatoryColumnHasData()) {
102 2
                    $this->handleInvalidMandatoryColumn();
103
104 2
                    if ($this->stopIfInvalid) {
105
                        return false;
106
                    }
107 2
                }
108
109 71
                if (!$this->validateSpecificFormat()) {
110 37
                    $this->handleInvalidFormat();
111
112 37
                    if ($this->stopIfInvalid) {
113
                        return false;
114
                    }
115 37
                }
116
117 70
                if (!$this->validatePattern()) {
118 7
                    $this->handleInvalidPattern();
119
120 7
                    if ($this->stopIfInvalid) {
121
                        return false;
122
                    }
123 7
                }
124 70
            }
125
126 70
            $this->rowNumber++;
127 70
        }
128
129 70
        $this->setRowsAnalysedStatistic();
130
131 70
        return $this->valid;
132
    }
133
134
135
    /**
136
     * Check that the specified row has the expected number of columns.
137
     * The expected number of columns is the number of columns in the CSV header row.
138
     *
139
     * @return boolean  Whether the current row has the expected number of columns.
140
     */
141 71
    private function checkRowHasExpectedColumnCount()
142
    {
143 71
        $this->columnCount = count($this->currentCsvRow);
144 71
        $this->expectedColumnCount = count(parent::$headerColumns);
145
146 71
        return ($this->expectedColumnCount === $this->columnCount);
147
    }
148
149
150
    /**
151
     * Set an error and update the application as the current row has an unexpected number of columns.
152
     *
153
     * @return  void
154
     */
155
    private function handleUnexpectedColumnCount()
156
    {
157
        $errorMessage = "Row $this->rowNumber has $this->columnCount columns but should have $this->expectedColumnCount.";
158
        $this->error->setError(self::ERROR_INCORRECT_COLUMN_COUNT, $errorMessage);
159
        $this->statistics->setErrorRow($this->rowNumber);
160
    }
161
162
163
    /**
164
     * Check whether the current column is mandatory and if so, whether it has data in it.
165
     *
166
     * @return  boolean Whether the column has data in it.
167
     */
168 71
    private function checkMandatoryColumnHasData()
169
    {
170 71
        if ($this->isColumnMandatory($this->schemaColumn)) {
171 70
            return ('' !== $this->currentCsvRow[$this->csvColumnPosition]);
172
        }
173
174 68
        return true;
175
    }
176
177
178
    /**
179
     * Set an error and update the application as the current column is mandatory and has no data in it.
180
     *
181
     * @return  void
182
     */
183 2 View Code Duplication
    private function handleInvalidMandatoryColumn()
184
    {
185 2
        $errorMessage = $this->schemaColumn->name . " on row $this->rowNumber is missing.";
186 2
        $this->error->setError(self::ERROR_REQUIRED_FIELD_MISSING_DATA, $errorMessage);
187 2
        $this->statistics->setErrorRow($this->rowNumber);
188 2
        $this->valid = false;
189 2
    }
190
191
192
    /**
193
     * Check that the data in the current field is of a valid format as specified in the schema for this column.
194
     * This instantiates and passed the data to the format validator for this field type.
195
     *
196
     * @return  boolean Whether the current field is of a valid format.
197
     */
198 71
    private function validateSpecificFormat()
199
    {
200 71
        $type = $this->getColumnType($this->schemaColumn);
201 71
        $this->format = $this->getColumnFormat($this->schemaColumn);
202 71
        $validator = $this->instantiateValidator(Analyse::VALIDATION_TYPE_FORMAT, $type);
203 70
        $validator->setInput($this->currentCsvRow[$this->csvColumnPosition]);
204
205 70
        return $validator->validateFormat($this->format);
206
    }
207
208
209
    /**
210
     * Set an error and update the application as the current data didn't match the specified format.
211
     *
212
     * @return  void
213
     */
214 37 View Code Duplication
    private function handleInvalidFormat()
215
    {
216 37
        $errorMessage  = "The data in column " . $this->schemaColumn->name ." on row $this->rowNumber doesn't ";
217 37
        $errorMessage .= "match the required format of $this->format.";
218 37
        $this->error->setError(self::ERROR_INVALID_FORMAT, $errorMessage);
219 37
        $this->statistics->setErrorRow($this->rowNumber);
220 37
        $this->valid = false;
221 37
    }
222
223
224
    /**
225
     * Get the pattern of the specified column.
226
     *
227
     * @return  string  The pattern or null if no pattern is specified.
228
     */
229 70
    private function getColumnPattern()
230
    {
231 70
        $propertyExists = property_exists($this->schemaColumn, 'constraints') &&
232 70
            property_exists($this->schemaColumn->constraints, 'pattern');
233
234 70
        return $propertyExists ? $this->schemaColumn->constraints->pattern : null;
235
    }
236
237
238
    /**
239
     * Check that the input matches the specified pattern.
240
     *
241
     * @return  boolean Is the data valid.
242
     */
243 70
    private function validatePattern()
244
    {
245 70
        $this->pattern = $this->getColumnPattern();
246 70
        $input = $this->currentCsvRow[$this->csvColumnPosition];
247
248 70
        if (is_null($this->pattern) || '' === $input) {
249 70
            return true;
250
        }
251
252 10
        return (false !== filter_var($input, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => $this->pattern]]));
253
    }
254
255
256
    /**
257
     * Set an error and update the application as the current data didn't match the specified pattern.
258
     *
259
     * @return  void
260
     */
261 7 View Code Duplication
    private function handleInvalidPattern()
262
    {
263 7
        $errorMessage  = "The data in column " . $this->schemaColumn->name . " on row $this->rowNumber doesn't ";
264 7
        $errorMessage .= "match the required pattern of $this->pattern.";
265 7
        $this->error->setError(self::ERROR_INVALID_PATTERN, $errorMessage);
266 7
        $this->statistics->setErrorRow($this->rowNumber);
267 7
        $this->valid = false;
268 7
    }
269
270
271
    /**
272
     * Add the number of rows analysed to the statistics.
273
     *
274
     * @return  void
275
     */
276 70
    private function setRowsAnalysedStatistic()
277
    {
278 70
        $this->statistics->setRowsAnalysed($this->rowNumber - 1);
279
    }
280
}