Passed
Push — master ( fd902b...0cd8c1 )
by George
02:56
created

Lexical::checkMandatoryColumnHasData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2
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 array   The current CSV row being analysed.
23
     */
24
    private $currentCsvRow;
25
26
    /**
27
     * @var int The position of the CSV column currently being analysed.
28
     */
29
    private $csvColumnPosition;
30
31
    /**
32
     * @var int The position of the current CSV row row in the CSV file.
33
     */
34
    private $rowNumber;
35
36
    /**
37
     * @var object  The schema definition for the column currently being analysed.
38
     */
39
    private $schemaColumn;
40
41
    /**
42
     * @var int The number of columns in the currently analysed row.
43
     */
44
    private $columnCount;
45
46
    /**
47
     * @var int The number of columns expected in each row.
48
     * This is taken from the CSV header row.
49
     */
50
    private $expectedColumnCount;
51
52
    /**
53
     * @var string  The pattern to validate the current field against.
54
     */
55
    private $pattern;
56
57
    /**
58
     * @var string  The format to validate the current field against.
59
     */
60
    private $format;
61
62
    /**
63
     * @var bool    Whether the file is valid.
64
     */
65
    private $valid;
66
67
68
    /**
69
     * Validate that all fields are of the correct type, format and pattern.
70
     * This also checks that each CSV row has the expected number of columns.
71
     *
72
     * @access  public
73
     *
74
     * @return  boolean Is all data lexically valid.
75
     */
76 28
    public function validate()
77
    {
78 28
        $this->valid = true;
79 28
        $this->rowNumber = 1;
80
81 28
        parent::rewindFilePointerToFirstData();
1 ignored issue
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (rewindFilePointerToFirstData() instead of validate()). Are you sure this is correct? If so, you might want to change this to $this->rewindFilePointerToFirstData().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
82
83 28
        while ($this->currentCsvRow = parent::loopThroughFileRows()) {
1 ignored issue
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (loopThroughFileRows() instead of validate()). Are you sure this is correct? If so, you might want to change this to $this->loopThroughFileRows().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
84 28
            if (!$this->checkRowHasExpectedColumnCount()) {
85
                $this->handleUnexpectedColumnCount();
86
            }
87
88 28
            for ($this->csvColumnPosition = 0; $this->csvColumnPosition < $this->columnCount; $this->csvColumnPosition++) {
89 28
                $this->schemaColumn = $this->getSchemaColumnFromCsvColumnPosition($this->csvColumnPosition);
90
91 28
                if (!$this->checkMandatoryColumnHasData()) {
92 2
                    $this->handleInvalidMandatoryColumn();
93
94 2
                    if ($this->stopIfInvalid) {
95
                        return false;
96
                    }
97 2
                }
98
99 28
                if (!$this->validateSpecificFormat()) {
100 8
                    $this->handleInvalidFormat();
101
102 8
                    if ($this->stopIfInvalid) {
103
                        return false;
104
                    }
105 8
                }
106
107 28
                if (!$this->validatePattern()) {
108
                    $this->handleInvalidPattern();
109
110
                    if ($this->stopIfInvalid) {
111
                        return false;
112
                    }
113
                }
114 28
            }
115
116 28
            $this->rowNumber++;
117 28
        }
118
119 28
        $this->setRowsAnalysedStatistic();
120
121 28
        return $this->valid;
122
    }
123
124
125
    /**
126
     * Check that the specified row has the expected number of columns.
127
     * The expected number of columns is the number of columns in the CSV header row.
128
     *
129
     * @return boolean  Whether the current row has the expected number of columns.
130
     */
131 28
    private function checkRowHasExpectedColumnCount()
132
    {
133 28
        $this->columnCount = count($this->currentCsvRow);
134 28
        $this->expectedColumnCount = count(parent::$headerColumns);
135
136 28
        return ($this->expectedColumnCount === $this->columnCount);
137
    }
138
139
140
    /**
141
     * Set an error and update the application as the current row has an unexpected number of columns.
142
     *
143
     * @access  private
144
     *
145
     * @return  void
146
     */
147
    private function handleUnexpectedColumnCount()
148
    {
149
        $errorMessage = "Row $this->rowNumber has $this->columnCount columns but should have $this->expectedColumnCount.";
150
        $this->setError(Analyse::ERROR_INCORRECT_COLUMN_COUNT, $errorMessage);
151
        $this->setErrorRowStatistic($this->rowNumber);
152
    }
153
154
155
    /**
156
     * Check whether the current column is mandatory and if so, whether it has data in it.
157
     *
158
     * @access  private
159
     *
160
     * @return  boolean Whether the column has data in it.
161
     */
162 28
    private function checkMandatoryColumnHasData()
163
    {
164 28
        if ($this->isColumnMandatory($this->schemaColumn)) {
165 28
            return ('' !== $this->currentCsvRow[$this->csvColumnPosition]);
166
        }
167
168 25
        return true;
169
    }
170
171
172
    /**
173
     * Set an error and update the application as the current column is mandatory and has no data in it.
174
     *
175
     * @access  private
176
     *
177
     * @return  void
178
     */
179 2 View Code Duplication
    private function handleInvalidMandatoryColumn()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
180
    {
181 2
        $errorMessage = $this->schemaColumn->name . " on row $this->rowNumber is missing.";
182 2
        $this->setError(Analyse::ERROR_REQUIRED_FIELD_MISSING_DATA, $errorMessage);
183 2
        $this->setErrorRowStatistic($this->rowNumber);
184 2
        $this->valid = false;
185 2
    }
186
187
188
    /**
189
     * Check that the data in the current field is of a valid format as specified in the schema for this column.
190
     * This instantiates and passed the data to the format validator for this field type.
191
     *
192
     * @access  private
193
     *
194
     * @return  boolean Whether the current field is of a valid format.
195
     */
196 28
    private function validateSpecificFormat()
197
    {
198 28
        $type = $this->getColumnType($this->schemaColumn);
199 28
        $this->format = $this->getColumnFormat($this->schemaColumn);
200 28
        $validator = $this->instantiateValidator(Analyse::VALIDATION_TYPE_FORMAT, $type);
201 28
        $validator->setInput($this->currentCsvRow[$this->csvColumnPosition]);
202
203 28
        return $validator->validateFormat($this->format);
204
    }
205
206
207
    /**
208
     * Set an error and update the application as the current data didn't match the specified format.
209
     *
210
     * @access  private
211
     *
212
     * @return  void
213
     */
214 8 View Code Duplication
    private function handleInvalidFormat()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
215
    {
216 8
        $errorMessage  = "The data in column " . $this->schemaColumn->name ." on row $this->rowNumber doesn't ";
217 8
        $errorMessage .= "match the required format of $this->format.";
218 8
        $this->setError(self::ERROR_INVALID_FORMAT, $errorMessage);
219 8
        $this->setErrorRowStatistic($this->rowNumber);
220 8
        $this->valid = false;
221 8
    }
222
223
224
    /**
225
     * Get the pattern of the specified column.
226
     *
227
     * @access  private
228
     *
229
     * @return  string  The pattern or null if no pattern is specified.
230
     */
231 28
    private function getColumnPattern()
232
    {
233 28
        $propertyExists = property_exists($this->schemaColumn, 'constraints') &&
234 28
            property_exists($this->schemaColumn->constraints, 'pattern');
235
236 28
        return $propertyExists ? $this->schemaColumn->constraints->pattern : null;
237
    }
238
239
240
    /**
241
     * Check that the input matches the specified pattern.
242
     *
243
     * @access  private
244
     *
245
     * @return  boolean Is the data valid.
246
     */
247 28
    private function validatePattern()
248
    {
249 28
        $this->pattern = $this->getColumnPattern();
250 28
        $input = $this->currentCsvRow[$this->csvColumnPosition];
251
252 28
        if (is_null($this->pattern) || '' === $input) {
253 28
            return true;
254
        }
255
256 2
        return (false !== filter_var($input, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => $this->pattern]]));
257
    }
258
259
260
    /**
261
     * Set an error and update the application as the current data didn't match the specified pattern.
262
     *
263
     * @access  private
264
     *
265
     * @return  void
266
     */
267 View Code Duplication
    private function handleInvalidPattern()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
268
    {
269
        $errorMessage  = "The data in column " . $this->schemaColumn->name . " on row $this->rowNumber doesn't ";
270
        $errorMessage .= "match the required pattern of $this->pattern.";
271
        $this->setError(self::ERROR_INVALID_PATTERN, $errorMessage);
272
        $this->setErrorRowStatistic($this->rowNumber);
273
        $this->valid = false;
274
    }
275
276
277
    /**
278
     * Add the number of rows analysed to the statistics.
279
     *
280
     * @access  private
281
     *
282
     * @return  void
283
     */
284 28
    private function setRowsAnalysedStatistic()
285
    {
286 28
        parent::$statistics['rows_analysed'] = ($this->rowNumber - 1);
287
    }
288
}