Passed
Push — master ( 160783...dd007d )
by George
03:52
created

ForeignKey::getForeignKeyCsvPositions()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 8.125

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 26
ccs 8
cts 16
cp 0.5
rs 8.439
cc 5
eloc 12
nc 5
nop 0
crap 8.125
1
<?php
2
namespace JsonTable\Analyse;
3
4
/**
5
 * Perform primary key analysis.
6
 *
7
 * @package JsonTable
8
 */
9
class ForeignKey extends Analyse implements AnalyseInterface
10
{
11
    /**
12
     * @var string The description for fields with invalid foreign keys.
13
     */
14
    const ERROR_INVALID_FOREIGN_KEY = 'There are <strong>%d</strong> fields that have invalid foreign keys:';
15
16
    /**
17
     * @var string  The name of the datapackage the current foreign key references.
18
     */
19
    private $dataPackage;
20
21
    /**
22
     * @var object  The validator.
23
     */
24
    private $validator;
25
26
    /**
27
     * @var object  The current foreign key being analysed.
28
     */
29
    private $foreignKey;
30
31
    /**
32
     * @var array   The position of the foreign key columns in the CSV file.
33
     */
34
    private $csvPositions = [];
35
36
    /**
37
     * @var string  The current foreign key field having it's CSV file position found.
38
     */
39
    private $csvFieldName;
40
41
    /**
42
     * @var int The current CSV row being analysed.
43
     */
44
    private $currentCsvRow;
45
46
47
48
    /**
49
     * Validate that any specified foreign key constraints have been met.
50
     *
51
     * @return  boolean Does the data meet the foreign key constraints.
52
     *
53
     * @throws  \Exception if a foreign key other than postgresql is specified.
54
     */
55 80
    public function validate()
56
    {
57 80
        if (false === property_exists(self::$schemaJson, 'foreignKeys')) {
58 1
            return true;
59
        }
60
61 79
        self::rewindFilePointerToFirstData();
62
63 79
        foreach (self::$schemaJson->foreignKeys as $foreignKey) {
64 79
            $this->foreignKey = $foreignKey;
65 79
            $this->dataPackage = $this->getForeignKeyPackage();
66
67 79
            if (!$this->checkValidDataPackageType()) {
68 7
                $this->handleInvalidDataPackageType();
69
            }
70
71 72
            $this->setValidator();
72 72
            $this->setFieldArrays();
73
74 72
            if (!$this->getForeignKeyCsvPositions()) {
75
                return true;
76
            }
77
78 72
            $this->currentCsvRow = 1;
79
80 72
            if (!$this->validateCsvRows()) {
81 72
                return false;
82
            }
83
84
            self::rewindFilePointerToFirstData();
85
        }
86
87
        return true;
88
    }
89
90
    
91
    /**
92
     * Get the package of the specified foreign key.
93
     *
94
     * @return  string  The package for the foreign key.
95
     */
96 79
    private function getForeignKeyPackage()
97
    {
98 79
        $propertyExists = property_exists($this->foreignKey->reference, 'datapackage');
99 79
        return $propertyExists ? $this->foreignKey->reference->datapackage : 'postgresql';
100
    }
101
102
103
    /**
104
     * Check that the data package for the current foreign key is a valid package type.
105
     *
106
     * @return  boolean Whether the data package is valid
107
     */
108 79
    private function checkValidDataPackageType()
109
    {
110 79
        return ('postgresql' === $this->dataPackage);
111
    }
112
113
114
    /**
115
     * Handle an invalid data package being referenced in a foreign key.
116
     *
117
     * @return  void
118
     *
119
     * @throws  \Exception if the data package is not valid.
120
     */
121 7
    private function handleInvalidDataPackageType()
122
    {
123 7
        throw new \Exception("Only postgresql foreign keys are currently supported.
124
                Please ensure that the datapackage attribute on all foreign keys is defined
125 7
                as &quot;database&quot; or is omitted.");
126
    }
127
128
129
    /**
130
     * Set the validator property.
131
     * The type of validator is taken from the type of datapackage.
132
     *
133
     * @return  void
134
     */
135 72
    private function setValidator()
136
    {
137 72
        $this->validator = $this->instantiateValidator(Analyse::VALIDATION_TYPE_FOREIGN_KEY, $this->dataPackage);
138 72
    }
139
140
141
    /**
142
     * Ensure that the "field" and "reference field" properties of the foreign key are both arrays.
143
     *
144
     * @return  void
145
     */
146 72
    private function setFieldArrays()
147
    {
148 72
        $this->foreignKey->fields = (array) $this->foreignKey->fields;
149 72
        $this->foreignKey->reference->fields = (array) $this->foreignKey->reference->fields;
150 72
    }
151
152
153
    /**
154
     * Loop through the CSV fields listed in the foreign
155
     * key and build up a list of CSV positions these relate to.
156
     *
157
     * @return boolean  Whether any CSV field positions have been found.
158
     *
159
     * @throws  \Exception if a foreign key field is not defined in the schema file.
160
     * @throws  \Exception if a multi-field foreign key field is not in the CSV file.
161
     */
162 72
    private function getForeignKeyCsvPositions()
163
    {
164 72
        foreach ($this->foreignKey->fields as $this->csvFieldName) {
165 72
            $this->csvFieldName = strtolower($this->csvFieldName);
166
167 72
            if (false === $this->getSchemaKeyFromName($this->csvFieldName)) {
168
                throw new \Exception("The foreign key field &quot;$this->csvFieldName&quot;
169
                    was not defined in the schema.");
170
            }
171
172 72
            $csvPosition = $this->getCsvPositionFromName($this->csvFieldName);
173
174 72
            if (false === $csvPosition) {
175
                if (1 !== count($this->foreignKey->fields)) {
176
                    throw new \Exception("The foreign key field &quot;$this->csvFieldName&quot;
177
                        was not in the CSV file but is required as part of a multi field foreign key.");
178
                }
179
180
                return false;
181
            }
182
183 72
            $this->csvPositions[] = $csvPosition;
184
185 72
            return true;
186
        }
187
    }
188
189
190
    /**
191
     * Analyse and check if each CSV row has a valid foreign key.
192
     *
193
     * @return  bool    Whether all the CSV rows are valid.
194
     */
195 72
    private function validateCsvRows()
196
    {
197 72
        while ($csvRow = self::loopThroughFileRows()) {
198 72
            $rowKeyParts = [];
199
200 72
            foreach ($this->csvPositions as $csvPosition) {
201 72
                $rowKeyParts[] = $csvRow[$csvPosition];
202 72
            }
203
204 72
            $csvValueHash = implode(', ', $rowKeyParts);
205
206 72
            if (!$this->validator->validate(
207 72
                $csvValueHash,
208 72
                $this->foreignKey->reference->resource,
209 72
                $this->foreignKey->reference->fields
210 72
            )) {
211 1
                $this->handleInvalidForeignKey($csvValueHash);
212
213 1
                if ($this->stopIfInvalid) {
214
                    return false;
215
                }
216 1
            }
217
218 72
            $this->currentCsvRow++;
219 72
        }
220 72
    }
221
222
223
    /**
224
     * Handle a CSV row having an invalid foreign key.
225
     *
226
     * @param   string  $csvValueHash   The foreign key hash that couldn't be matched.
227
     *
228
     * @return  void
229
     */
230 1 View Code Duplication
    private function handleInvalidForeignKey($csvValueHash)
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...
231
    {
232 1
        $csvFieldCsv = implode(', ', $this->foreignKey->fields);
233 1
        $errorMessage = "The value(s) of &quot;$csvValueHash&quot; in column(s) $csvFieldCsv
234 1
                    on row $this->currentCsvRow doesn't match a foreign key.";
235
236 1
        $this->error->setError(self::ERROR_INVALID_FOREIGN_KEY, $errorMessage);
237 1
        $this->statistics->setErrorRow($this->currentCsvRow);
238
    }
239
}