Passed
Push — master ( 163405...503637 )
by George
02:38
created

ForeignKey   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 65.52%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 16
c 2
b 0
f 0
lcom 1
cbo 1
dl 0
loc 164
ccs 38
cts 58
cp 0.6552
rs 10

4 Methods

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