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 "database" 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 "$this->csvFieldName" |
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 "$this->csvFieldName" |
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) |
|
|
|
|
231
|
|
|
{ |
232
|
1 |
|
$csvFieldCsv = implode(', ', $this->foreignKey->fields); |
233
|
1 |
|
$errorMessage = "The value(s) of "$csvValueHash" 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
|
|
|
} |
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.