Passed
Push — master ( be9b76...46473c )
by George
03:25
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 96.39%

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 80
cts 83
cp 0.9639
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A checkRowHasExpectedColumnCount() 0 7 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
A handleInvalidPattern() 8 8 1
C validate() 0 49 10
A handleUnexpectedColumnCount() 0 6 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 73
    public function validate()
85
    {
86 73
        $this->valid = true;
87 73
        $this->rowNumber = 1;
88
89 73
        parent::rewindFilePointerToFirstData();
90
91 73
        while ($currentCsvRow = parent::loopThroughFileRows()) {
92 73
            $this->currentCsvRow = $currentCsvRow;
93
            
94 73
            if (!$this->checkRowHasExpectedColumnCount()) {
95 1
                $this->handleUnexpectedColumnCount();
96 1
            }
97
98 73
            for ($this->csvColumnPosition = 0; $this->csvColumnPosition < $this->columnCount; $this->csvColumnPosition++) {
99 73
                $this->schemaColumn = $this->getSchemaColumnFromCsvColumnPosition($this->csvColumnPosition);
100
101 73
                if (!$this->checkMandatoryColumnHasData()) {
102 2
                    $this->handleInvalidMandatoryColumn();
103
104 2
                    if ($this->stopIfInvalid) {
105
                        return false;
106
                    }
107 2
                }
108
109 73
                if (!$this->validateSpecificFormat()) {
110 37
                    $this->handleInvalidFormat();
111
112 37
                    if ($this->stopIfInvalid) {
113
                        return false;
114
                    }
115 37
                }
116
117 72
                if (!$this->validatePattern()) {
118 7
                    $this->handleInvalidPattern();
119
120 7
                    if ($this->stopIfInvalid) {
121
                        return false;
122
                    }
123 7
                }
124 72
            }
125
126 72
            $this->rowNumber++;
127 72
        }
128
129 72
        $this->setRowsAnalysedStatistic();
130
131 72
        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 73
    private function checkRowHasExpectedColumnCount()
142
    {
143 73
        $this->columnCount = count($this->currentCsvRow);
144 73
        $this->expectedColumnCount = count(parent::$headerColumns);
145
146 73
        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 1
    private function handleUnexpectedColumnCount()
156
    {
157 1
        $errorMessage = "Row $this->rowNumber has $this->columnCount columns but should have $this->expectedColumnCount.";
158 1
        $this->error->setError(self::ERROR_INCORRECT_COLUMN_COUNT, $errorMessage);
159 1
        $this->statistics->setErrorRow($this->rowNumber);
160 1
    }
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 73
    private function checkMandatoryColumnHasData()
169
    {
170 73
        if ($this->isColumnMandatory($this->schemaColumn)) {
171 72
            return ('' !== $this->currentCsvRow[$this->csvColumnPosition]);
172
        }
173
174 69
        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 73
    private function validateSpecificFormat()
199
    {
200 73
        $type = $this->getColumnType($this->schemaColumn);
201 73
        $this->format = $this->getColumnFormat($this->schemaColumn);
202 73
        $validator = $this->instantiateValidator(Analyse::VALIDATION_TYPE_FORMAT, $type);
203 72
        $validator->setInput($this->currentCsvRow[$this->csvColumnPosition]);
204
205 72
        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 72
    private function getColumnPattern()
230
    {
231 72
        $propertyExists = property_exists($this->schemaColumn, 'constraints') &&
232 72
            property_exists($this->schemaColumn->constraints, 'pattern');
233
234 72
        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 72
    private function validatePattern()
244
    {
245 72
        $this->pattern = $this->getColumnPattern();
246 72
        $input = $this->currentCsvRow[$this->csvColumnPosition];
247
248 72
        if (is_null($this->pattern) || '' === $input) {
249 72
            return true;
250
        }
251
252 11
        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 72
    private function setRowsAnalysedStatistic()
277
    {
278 72
        $this->statistics->setRowsAnalysed($this->rowNumber - 1);
279
    }
280
}