1 | <?php |
||
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 | /** |
||
23 | * Validate that any specified foreign key constraints have been met. |
||
24 | * |
||
25 | * @return boolean Does the data meet the foreign key constraints. |
||
26 | * |
||
27 | * @throws \Exception if a foreign key other than postgresql is specified. |
||
28 | */ |
||
29 | 79 | public function validate() |
|
30 | { |
||
31 | 79 | if (false === property_exists(self::$schemaJson, 'foreignKeys')) { |
|
32 | 1 | return true; |
|
33 | } |
||
34 | |||
35 | 78 | self::rewindFilePointerToFirstData(); |
|
36 | |||
37 | 78 | foreach (self::$schemaJson->foreignKeys as $foreignKey) { |
|
38 | 78 | $this->dataPackage = $this->getForeignKeyPackage($foreignKey); |
|
39 | |||
40 | 78 | if (!$this->checkValidDataPackageType()) { |
|
41 | 7 | $this->handleInvalidDataPackageType(); |
|
42 | } |
||
43 | |||
44 | 71 | $validator = $this->instantiateValidator(Analyse::VALIDATION_TYPE_FOREIGN_KEY, $this->dataPackage); |
|
45 | |||
46 | // Get the fields in the CSV and the resource for this foreign key. |
||
47 | 71 | $csvFields = (array) $foreignKey->fields; |
|
48 | 71 | $referenceFields = (array) $foreignKey->reference->fields; |
|
49 | 71 | $csvPositions = []; |
|
50 | |||
51 | // Loop through the CSV fields listed in the foreign |
||
52 | // key and build up a list of CSV positions these relate to. |
||
53 | 71 | foreach ($csvFields as $csvFieldName) { |
|
54 | // Ensure the field name is lowercase as all field names have been lower-cased. |
||
55 | 71 | $csvFieldName = strtolower($csvFieldName); |
|
56 | |||
57 | // Check that the field exists in the schema. |
||
58 | 71 | if (false === $this->getSchemaKeyFromName($csvFieldName)) { |
|
59 | throw new \Exception("The foreign key field "$csvFieldName" |
||
60 | was not defined in the schema."); |
||
61 | } |
||
62 | |||
63 | 71 | $csvPosition = $this->getCsvPositionFromName($csvFieldName); |
|
64 | |||
65 | 71 | if (false === $csvPosition) { |
|
66 | if (1 !== count($csvFields)) { |
||
67 | // This field is part of a multi field foreign key. |
||
68 | // Throw an error as this key cannot be validated. |
||
69 | throw new \Exception("The foreign key field "$csvFieldName" |
||
70 | was not in the CSV file but is required as part of a multi field foreign key."); |
||
71 | } |
||
72 | |||
73 | // This is the only field in the foreign key so skip the validation of this foreign key. |
||
74 | continue 2; |
||
75 | } |
||
76 | |||
77 | // Add the position of this foreign key related CSV field to the container |
||
78 | // so the data for it can be retrieved. |
||
79 | 71 | $csvPositions[] = $csvPosition; |
|
80 | 71 | } |
|
81 | |||
82 | // Set the row flag. |
||
83 | 71 | $row = 1; |
|
84 | |||
85 | // Read each row in the file. |
||
86 | 71 | while ($csvRow = self::loopThroughFileRows()) { |
|
87 | // Define the container for the foreign key parts for this row. |
||
88 | 71 | $rowKeyParts = []; |
|
89 | |||
90 | // Build up the CSV foreign key hash using the CSV field positions calculated above. |
||
91 | 71 | foreach ($csvPositions as $csvPosition) { |
|
92 | 71 | $rowKeyParts[] = $csvRow[$csvPosition]; |
|
93 | 71 | } |
|
94 | |||
95 | 71 | $csvValueHash = implode(', ', $rowKeyParts); |
|
96 | |||
97 | // Validate the foreign key. |
||
98 | 71 | if (!$validator->validate( |
|
99 | 71 | $csvValueHash, |
|
100 | 71 | $foreignKey->reference->resource, |
|
101 | $referenceFields |
||
102 | 71 | )) { |
|
103 | // This hash didn't match a foreign key. |
||
104 | 1 | $csvFieldCsv = implode(', ', $csvFields); |
|
105 | 1 | $errorMessage = "The value(s) of "$csvValueHash" in column(s) $csvFieldCsv |
|
106 | 1 | on row $row doesn't match a foreign key."; |
|
107 | |||
108 | 1 | $this->error->setError(self::ERROR_INVALID_FOREIGN_KEY, $errorMessage); |
|
109 | 1 | $this->statistics->setErrorRow($row); |
|
110 | |||
111 | 1 | if ($this->stopIfInvalid) { |
|
112 | return false; |
||
113 | } |
||
114 | 1 | } |
|
115 | |||
116 | 71 | $row++; |
|
117 | 71 | } |
|
118 | |||
119 | 71 | self::rewindFilePointerToFirstData(); |
|
120 | 71 | } |
|
121 | |||
122 | 71 | return true; |
|
123 | } |
||
124 | |||
125 | |||
126 | /** |
||
127 | * Get the package of the specified foreign key. |
||
128 | * |
||
129 | * @param object $foreignKey The foreign key object to examine. |
||
130 | * |
||
131 | * @return string The package for the foreign key. |
||
132 | */ |
||
133 | 78 | private function getForeignKeyPackage($foreignKey) |
|
138 | |||
139 | |||
140 | /** |
||
141 | * Check that the data package for the current foreign key is a valid package type. |
||
142 | * |
||
143 | * @return boolean Whether the data package is valid |
||
144 | */ |
||
145 | 78 | private function checkValidDataPackageType() |
|
149 | |||
150 | |||
151 | /** |
||
152 | * Handle an invalid data package being referenced in a foreign key. |
||
153 | * |
||
154 | * @return void |
||
155 | * |
||
156 | * @throws \Exception if the data package is not valid. |
||
157 | */ |
||
158 | 7 | private function handleInvalidDataPackageType() |
|
164 | } |