Passed
Push — master ( 0cd8c1...21e99f )
by George
03:10
created

Analyse::getErrorRowPercent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
namespace JsonTable\Analyse;
3
4
use \JsonTable\Base;
5
6
/**
7
 * Analyse data to ensure it validates against a JSON table schema.
8
 *
9
 * @package    JSON table
10
 */
11
class Analyse extends Base implements AnalyseInterface
12
{
13
    /**
14
     * @var string The description for missing mandatory columns.
15
     */
16
    const ERROR_REQUIRED_COLUMN_MISSING = '<strong>%d</strong> required column(s) missing:';
17
18
    /**
19
     * @var string The description for CSV columns that are not in the schema.
20
     */
21
    const ERROR_UNSPECIFIED_COLUMN = '<strong>%d</strong> unexpected column(s):';
22
23
    /**
24
     * @var string The description for rows with missing columns.
25
     */
26
    const ERROR_INCORRECT_COLUMN_COUNT = 'There are the wrong number of columns';
27
28
    /**
29
     * @var string The description for rows with missing columns.
30
     */
31
    const ERROR_REQUIRED_FIELD_MISSING_DATA = 'There are <strong>%d</strong> required fields with missing data:';
32
33
    /**
34
     * @var string The format validation type.
35
     */
36
    const VALIDATION_TYPE_FORMAT = 'Format';
37
38
    /**
39
     * @var string The foreign key validation type.
40
     */
41
    const VALIDATION_TYPE_FOREIGN_KEY = 'ForeignKey';
42
43
    /**
44
     * @var boolean Should the analysis stop when an error is found.
45
     */
46
    protected $stopIfInvalid;
47
48
    /**
49
     * @var Statistics  Statistics information regarding the analysis.
50
     */
51
    protected $statistics;
52
53
    /**
54
     * @var Error  Details of errors found during the analysis.
55
     */
56
    protected $error;
57
58
59
    /**
60
     * Set the dependencies if they've been provided.
61
     *
62
     * @param   Statistics  $statistics Statistics information regarding the analysis. Optional.
63
     * @param   Error       $error      Details of errors found during the analysis. Optional.
64
     */
65 41
    public function __construct(Statistics $statistics = null, Error $error = null)
66
    {
67 41
        $this->statistics = (is_null($statistics)) ? new Statistics() : $statistics;
68 41
        $this->error = (is_null($error)) ? new Error() : $error;
69 41
    }
70
71
72
    /**
73
     * Analyse the specified file against the loaded schema.
74
     *
75
     * @param   boolean $stopIfInvalid Should the analysis stop when the file is found to be invalid.
76
     *                                          The default is false.
77
     *
78
     * @return  boolean true if the file passes the validation and false if not.
79
     */
80 30
    public function validate($stopIfInvalid = false)
81
    {
82 30
        $this->stopIfInvalid = (bool) $stopIfInvalid;
83
84 30
        $continueAnalysis = true;
85
86 30
        self::openFile();
87 30
        self::setCsvHeaderColumns();
88
89 30
        if (!$this->validateMandatoryColumns()) {
90 2
            $continueAnalysis = false;
91 2
        }
92
93 30
        if ($continueAnalysis && !$this->validateUnspecifiedColumns() && $this->stopIfInvalid) {
94
            $continueAnalysis = false;
95
        }
96
97 30
        $analyseLexical = new Lexical($this->statistics);
98
99 30
        if ($continueAnalysis && !$analyseLexical->validate() && $this->stopIfInvalid) {
100
            $continueAnalysis = false;
101
        }
102
103 30
        $analysePrimaryKey = new PrimaryKey($this->statistics);
104
        
105 30
        if ($continueAnalysis && !$analysePrimaryKey->validate() && $this->stopIfInvalid) {
106
            $continueAnalysis = false;
107
        }
108
109 30
        if ($continueAnalysis) {
110 28
            $analyseForeignKey = new ForeignKey($this->statistics);
111 28
            $analyseForeignKey->validate();
112 28
        }
113
114 30
        return $this->isFileValid();
115
    }
116
117
118
    /**
119
     * Return all errors.
120
     *
121
     * @return  array   The error messages.
122
     */
123 2
    public function getErrors()
124
    {
125 2
        return $this->error->getErrors();
126
    }
127
128
129
    /**
130
     * Return the statistics about this analysis.
131
     *
132
     * @return  array   The statistics.
133
     */
134 10
    public function getStatistics()
135
    {
136 10
        return $this->statistics->getStatistics();
137
    }
138
139
140
    /**
141
     * Validate that all mandatory columns are present.
142
     *
143
     * @return boolean Are all mandatory columns present.
144
     */
145 30
    private function validateMandatoryColumns()
146
    {
147 30
        $validMandatoryColumns = true;
148
149 30
        foreach (self::$schemaJson->fields as $field) {
150 30
            if ($this->isColumnMandatory($field)) {
151 30
                if (!in_array($field->name, self::$headerColumns)) {
152 2
                    $this->error->setError(Analyse::ERROR_REQUIRED_COLUMN_MISSING, $field->name);
153 2
                    $validMandatoryColumns = false;
154
155 2
                    if ($this->stopIfInvalid) {
156
                        return false;
157
                    }
158 2
                }
159 30
            }
160 30
        }
161
162 30
        return $validMandatoryColumns;
163
    }
164
165
166
    /**
167
     * Check that there are no columns in the CSV that are not specified in the schema.
168
     *
169
     * @return boolean Are all the CSV columns specified in the schema.
170
     */
171 28
    private function validateUnspecifiedColumns()
172
    {
173 28
        $validUnspecifiedColumns = true;
174
175 28
        foreach (self::$headerColumns as $csvColumnName) {
176 28
            if (false === $this->getSchemaKeyFromName($csvColumnName)) {
177
                $this->error->setError(Analyse::ERROR_UNSPECIFIED_COLUMN, $csvColumnName);
178
                $validUnspecifiedColumns = false;
179
180
                if ($this->stopIfInvalid) {
181
                    return false;
182
                }
183
            }
184 28
        }
185
186 28
        return $validUnspecifiedColumns;
187
    }
188
189
190
    /**
191
     * Check if the specified column is mandatory.
192
     *
193
     * @param   object  $schemaColumn    The schema column object to examine.
194
     *
195
     * @return  boolean Whether the column is mandatory.
196
     */
197 30
    protected function isColumnMandatory($schemaColumn)
198
    {
199 30
        $propertyExists = property_exists($schemaColumn, 'constraints') &&
200 30
                              property_exists($schemaColumn->constraints, 'required') &&
201 30
                              (true === $schemaColumn->constraints->required);
202 30
        return $propertyExists;
203
    }
204
205
206
    /**
207
     * Load and instantiate the specified validator.
208
     *
209
     * @param string $validationType The type of validator to load.
210
     * @param string $type The type being validated.
211
     *                            For formats this will be the field type.
212
     *                            For foreign keys this will be the datapackage type
213
     *
214
     * @return object The validation object. Throws an exception on error.
215
     *
216
     * @throws  \Exception if the validator file couldn't be loaded.
217
     * @throws  \Exception if the validator class definition couldn't be found.
218
     */
219 28
    protected function instantiateValidator($validationType, $type)
220
    {
221
        // For format validation, "Date", "datetime" and "time" all follow the same schema definition rules
222
        // so just use the datetime format for them all.
223 28
        if (Analyse::VALIDATION_TYPE_FORMAT === $validationType && ('date' === $type || 'time' === $type)) {
224 2
            $type = 'datetime';
225 2
        }
226
227 28
        $typeClassName = ucwords($type) . 'Validator';
228 28
        $validatorFile = dirname(dirname(__FILE__)) . "/Validate/$validationType/$typeClassName.php";
229
230 28
        if (!file_exists($validatorFile) || !is_readable($validatorFile)) {
231
            throw new \Exception("Could not load the validator file for $validationType $type.");
232
        }
233
234 28
        include_once $validatorFile;
235
236 28
        $validatorClass = "\\JsonTable\\Validate\\$validationType\\$typeClassName";
237
238 28
        if (!class_exists($validatorClass)) {
239
            throw new \Exception("Could not find the validator class $validatorClass");
240
        }
241
242 28
        return new $validatorClass($type);
243
    }
244
245
246
    /**
247
     * Check if the file was found to be valid.
248
     * This checks for any validation errors.
249
     *
250
     * @return  boolean Is the file valid.
251
     */
252 30
    private function isFileValid()
253
    {
254 30
        return (0 === count($this->error->getErrors()));
255
    }
256
}
257