Completed
Push — master ( 7fd5f6...af8ac7 )
by James Ekow Abaka
01:40
created

DataOperations   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 210
Duplicated Lines 2.86 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 6%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 37
lcom 1
cbo 7
dl 6
loc 210
ccs 6
cts 100
cp 0.06
rs 8.6
c 6
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 6 6 1
A doValidate() 0 8 2
A getData() 0 3 1
A isItemDeletable() 0 7 2
C doSave() 0 42 7
C saveRecord() 0 69 10
A validate() 0 12 3
B isPrimaryKeySet() 0 11 8
A assignValue() 0 7 2
A getInvalidFields() 0 3 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/*
4
 * The MIT License
5
 *
6
 * Copyright 2015 ekow.
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
 * THE SOFTWARE.
25
 */
26
27
namespace ntentan\nibii;
28
29
/**
30
 * Description of DataOperations
31
 *
32
 * @author ekow
33
 */
34
class DataOperations {
35
36
    private $wrapper;
37
38
    /**
39
     * Private instance of driver adapter
40
     *
41
     * @var DriverAdapter
42
     */
43
    private $adapter;
44
45
    /**
46
     *
47
     * @var array
48
     */
49
    private $data;
50
    private $invalidFields;
51
    private $hasMultipleData;
52
    private $driver;
53
    private $container;
54
55
    const MODE_SAVE = 0;
56
    const MODE_UPDATE = 1;
57
58 1 View Code Duplication
    public function __construct(ORMContext $context, RecordWrapper $wrapper, DriverAdapter $adapter) {
59 1
        $this->wrapper = $wrapper;
60 1
        $this->adapter = $adapter;
61 1
        $this->driver = $context->getDbContext()->getDriver();
62 1
        $this->container = $context->getContainer();
63 1
    }
64
65
    public function doSave($hasMultipleData) {
66
        $this->hasMultipleData = $hasMultipleData;
67
        $invalidFields = [];
68
        $data = $this->wrapper->getData();
69
        
70
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
71
        $singlePrimaryKey = null;
0 ignored issues
show
Unused Code introduced by
$singlePrimaryKey is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
72
        $succesful = true;
73
74
        if (count($primaryKey) == 1) {
75
            $singlePrimaryKey = $primaryKey[0];
0 ignored issues
show
Unused Code introduced by
$singlePrimaryKey is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
76
        }
77
78
        // Assign an empty array to force a validation error for empty models
79
        if (empty($data)) {
80
            $data = [[]];
81
        }
82
83
        $this->driver->beginTransaction();
84
85
        foreach ($data as $i => $datum) {
86
            $status = $this->saveRecord($datum, $primaryKey);
0 ignored issues
show
Documentation introduced by
$primaryKey is of type array, but the function expects a object<ntentan\nibii\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
87
            $data[$i] = $datum;
88
89
            if (!$status['success']) {
90
                $succesful = false;
91
                $invalidFields[$i] = $status['invalid_fields'];
92
                $this->driver->rollback();
93
                break;
94
            }
95
        }
96
97
        if ($succesful) {
98
            $this->driver->commit();
99
        } else {
100
            $this->assignValue($this->invalidFields, $invalidFields);
101
        }
102
103
        $this->wrapper->setData($hasMultipleData ? $data : $data[0]);
104
105
        return $succesful;
106
    }
107
    
108
    public function doValidate() {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
109
        $record = $this->wrapper->getData()[0];
110
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
111
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
112
        return $this->validate(
113
            $record, $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE
114
        );
115
    }
116
117
    /**
118
     * Save an individual record.
119
     * 
120
     * @param array $record The record to be saved
121
     * @param type $primaryKey The primary keys of the record
122
     * @return boolean
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
123
     */
124
    private function saveRecord(&$record, $primaryKey) {
125
        $status = [
126
            'success' => true,
127
            'pk_assigned' => null,
128
            'invalid_fields' => []
129
        ];
130
131
        // Determine if the primary key of the record is set.
132
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
133
134
        // Reset the data in the model to contain only the data to be saved
135
        $this->wrapper->setData($record);
136
137
        // Run preUpdate or preSave callbacks on models and behaviours
138
        if ($pkSet) {
139
            $this->wrapper->preUpdateCallback();
140
            $record = $this->wrapper->getData();
141
            $record = reset($record) === false ? [] : reset($record);
142
        } else {
143
            $this->wrapper->preSaveCallback();
144
            $record = $this->wrapper->getData();
145
            $record = reset($record) === false ? [] : reset($record);
146
        }
147
148
        // Validate the data
149
        $validity = $this->validate(
150
            $record, $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE
151
        );
152
153
        // Exit if data is invalid
154
        if ($validity !== true) {
155
            $status['invalid_fields'] = $validity;
156
            $status['success'] = false;
157
            return $status;
158
        }
159
        
160
        // Save any relationships that are attached to the data
161
        $relationships = $this->wrapper->getDescription()->getRelationships();
162
        $presentRelationships = [];
163
        
164
        foreach($relationships ?? [] as $model => $relationship) {
165
            if(isset($record[$model])) {
166
                $relationship->preSave($record, $record[$model]);
167
                $presentRelationships[$model] = $relationship;
168
            }
169
        }
170
        
171
        // Assign the data to the wrapper again
172
        $this->wrapper->setData($record);
173
174
        // Update or save the data and run post callbacks
175
        if ($pkSet) {
176
            $this->adapter->update($record);
177
            $this->wrapper->postUpdateCallback();
178
        } else {
179
            $this->adapter->insert($record);
180
            $keyValue = $this->driver->getLastInsertId();
181
            $this->wrapper->{$primaryKey[0]} = $keyValue;
182
            $this->wrapper->postSaveCallback($keyValue);
183
        }
184
        
185
        // Reset the data so it contains any modifications made by callbacks
186
        $record = $this->wrapper->getData()[0];
187
        foreach($presentRelationships as $model => $relationship) {
188
            $relationship->postSave($record);
189
        }        
190
191
        return $status;
192
    }
193
194
    private function validate($data, $mode) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
195
        $validator = $this->container->resolve(
196
            ModelValidator::class, ['model' => $this->wrapper, 'mode' => $mode]
197
        );
198
        $errors = [];
199
        
200
        if (!$validator->validate($data)) {
201
            $errors = $validator->getInvalidFields();
202
        }
203
        $errors = $this->wrapper->onValidate($errors);
204
        return empty($errors) ? true : $errors;
205
    }
206
207
    private function isPrimaryKeySet($primaryKey, $data) {
208
        if (is_string($primaryKey) && ($data[$primaryKey] !== null || $data[$primaryKey] !== '')) {
209
            return true;
210
        }
211
        foreach ($primaryKey as $keyField) {
212
            if (!isset($data[$keyField]) || $data[$keyField] === null || $data[$keyField] === '') {
213
                return false;
214
            }
215
        }
216
        return true;
217
    }
218
219
    private function assignValue(&$property, $value) {
220
        if ($this->hasMultipleData) {
221
            $property = $value;
222
        } else {
223
            $property = $value[0];
224
        }
225
    }
226
227
    public function getData() {
228
        return $this->data;
229
    }
230
231
    public function getInvalidFields() {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
232
        return $this->invalidFields;
233
    }
234
235
    public function isItemDeletable($primaryKey, $data) {
236
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
237
            return true;
238
        } else {
239
            return false;
240
        }
241
    }
242
243
}
244