Passed
Push — master ( 79226f...be9b76 )
by George
03:45
created

ForeignKey   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 86.21%

Importance

Changes 4
Bugs 1 Features 0
Metric Value
wmc 16
c 4
b 1
f 0
lcom 1
cbo 3
dl 0
loc 156
ccs 50
cts 58
cp 0.8621
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getForeignKeyPackage() 0 5 2
A checkValidDataPackageType() 0 4 1
C validate() 0 95 12
A handleInvalidDataPackageType() 0 6 1
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
    /**
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 &quot;$csvFieldName&quot;
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 &quot;$csvFieldName&quot;
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 &quot;$csvValueHash&quot; 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)
134
    {
135 78
        $propertyExists = property_exists($foreignKey->reference, 'datapackage');
136 78
        return $propertyExists ? $foreignKey->reference->datapackage : 'postgresql';
137
    }
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()
146
    {
147 78
        return ('postgresql' === $this->dataPackage);
148
    }
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()
159
    {
160 7
        throw new \Exception("Only postgresql foreign keys are currently supported.
161
                Please ensure that the datapackage attribute on all foreign keys is defined
162 7
                as &quot;database&quot; or is omitted.");
163
    }
164
}