Completed
Push — master ( 4c6c98...8c8647 )
by James Ekow Abaka
02:34
created

DataOperations   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 212
Duplicated Lines 2.83 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 84.91%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 36
c 3
b 0
f 0
lcom 1
cbo 7
dl 6
loc 212
ccs 90
cts 106
cp 0.8491
rs 8.8

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 6 6 1
C doSave() 0 41 7
A doValidate() 0 8 2
B saveRecord() 0 58 7
A validate() 0 20 4
B isPrimaryKeySet() 0 16 7
A assignValue() 0 7 2
A getData() 0 3 1
A getInvalidFields() 0 3 1
A isItemDeletable() 0 7 2
A runBehaviours() 0 6 2

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
     *
40
     * @var DriverAdapter
41
     */
42
    private $adapter;
43
    private $data;
44
    private $invalidFields;
45
    private $hasMultipleData;
46
    private $driver;
47
    private $container;
48
49
    const MODE_SAVE = 0;
50
    const MODE_UPDATE = 1;
51
52 35 View Code Duplication
    public function __construct(ORMContext $context, RecordWrapper $wrapper, DriverAdapter $adapter) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
53 35
        $this->wrapper = $wrapper;
54 35
        $this->adapter = $adapter;
55 35
        $this->driver = $context->getDbContext()->getDriver();
56 35
        $this->container = $context->getContainer();
57 35
    }
58
59 10
    public function doSave($hasMultipleData) {
60 10
        $this->hasMultipleData = $hasMultipleData;
61 10
        $invalidFields = [];
62 10
        $data = $this->wrapper->getData();
63 10
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
64 10
        $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...
65 10
        $succesful = true;
66
67 10
        if (count($primaryKey) == 1) {
68 10
            $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...
69
        }
70
71
        // Assign an empty array to force a validation error for empty models
72 10
        if (empty($data)) {
73 2
            $data = [[]];
74
        }
75
76 10
        $this->driver->beginTransaction();
77
78 10
        foreach ($data as $i => $datum) {
79 10
            $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...
80 10
            $data[$i] = $datum;
81
82 10
            if (!$status['success']) {
83 4
                $succesful = false;
84 4
                $invalidFields[$i] = $status['invalid_fields'];
85 4
                $this->driver->rollback();
86 10
                break;
87
            }
88
        }
89
90 10
        if ($succesful) {
91 6
            $this->driver->commit();
92
        } else {
93 4
            $this->assignValue($this->invalidFields, $invalidFields);
94
        }
95
96 10
        $this->wrapper->setData($hasMultipleData ? $data : $data[0]);
97
98 10
        return $succesful;
99
    }
100
    
101
    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...
102
        $record = $this->wrapper->getData()[0];
103
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
104
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
105
        return $this->validate(
106
            $record, $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE
107
        );
108
    }
109
110
    /**
111
     * Save an individual record.
112
     * 
113
     * @param array $record The record to be saved
114
     * @param type $primaryKey The primary keys of the record
115
     * @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...
116
     */
117 10
    private function saveRecord(&$record, $primaryKey) {
118
        $status = [
119 10
            'success' => true,
120
            'pk_assigned' => null,
121
            'invalid_fields' => []
122
        ];
123
124
        // Determine if the primary key of the record is set.
125 10
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
126
127
        // Reset the data in the model to contain only the data to be saved
128 10
        $this->wrapper->setData($record);
129
130
        // Run preUpdate or preSave callbacks on models and behaviours
131 10
        if ($pkSet) {
132 2
            $this->wrapper->preUpdateCallback();
133 2
            $record = $this->wrapper->getData();
134 2
            $record = reset($record) === false ? [] : reset($record);
135 2
            $record = $this->runBehaviours('preUpdateCallback', [$record]);
136
        } else {
137 8
            $this->wrapper->preSaveCallback();
138 8
            $record = $this->wrapper->getData();
139 8
            $record = reset($record) === false ? [] : reset($record);
140 8
            $record = $this->runBehaviours('preSaveCallback', [$record]);
141
        }
142
143
        // Validate the data
144 10
        $validity = $this->validate(
145 10
            $record, $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE
146
        );
147
148
        // Exit if data is invalid
149 10
        if ($validity !== true) {
150 4
            $status['invalid_fields'] = $validity;
151 4
            $status['success'] = false;
152 4
            return $status;
153
        }
154
155
        // Assign the data to the wrapper again
156 6
        $this->wrapper->setData($record);
157
158
        // Update or save the data and run post callbacks
159 6
        if ($pkSet) {
160 2
            $this->adapter->update($record);
161 2
            $this->wrapper->postUpdateCallback();
162 2
            $this->runBehaviours('postUpdateCallback', [$record]);
163
        } else {
164 4
            $this->adapter->insert($record);
165 4
            $keyValue = $this->driver->getLastInsertId();
166 4
            $this->wrapper->{$primaryKey[0]} = $keyValue;
167 4
            $this->wrapper->postSaveCallback($keyValue);
168 4
            $this->runBehaviours('postSaveCallback', [$record, $keyValue]);
169
        }
170
171
        // Reset the data so it contains any modifications made by callbacks
172 6
        $record = $this->wrapper->getData()[0];
173 6
        return $status;
174
    }
175
176 10
    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...
177 10
        $valid = true;
178 10
        $validator = $this->container->resolve(
179 10
            ModelValidator::class, ['model' => $this->wrapper, 'mode' => $mode]
180
        );
181
182 10
        if (!$validator->validate($data)) {
183 4
            $valid = false;
184
        }
185
186 10
        if ($valid) {
187 6
            $valid = $this->wrapper->onValidate();
188
        }
189
190 10
        if ($valid === false) {
191 4
            $valid = $validator->getInvalidFields();
192
        }
193
194 10
        return $valid;
195
    }
196
197 10
    private function isPrimaryKeySet($primaryKey, $data) {
198 10
        if (is_string($primaryKey)) {
199
            if (isset($data[$primaryKey])) {
200
                return true;
201
            }
202
        }
203 10
        foreach ($primaryKey as $keyField) {
204 10
            if (!isset($data[$keyField])) {
205 8
                break;
206
            }
207 2
            if ($data[$keyField] !== '' && $data[$keyField] !== null) {
208 2
                return true;
209
            }
210
        }
211 8
        return false;
212
    }
213
214 4
    private function assignValue(&$property, $value) {
215 4
        if ($this->hasMultipleData) {
216
            $property = $value;
217
        } else {
218 4
            $property = $value[0];
219
        }
220 4
    }
221
222
    public function getData() {
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...
223
        return $this->data;
224
    }
225
226 10
    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...
227 10
        return $this->invalidFields;
228
    }
229
230
    public function isItemDeletable($primaryKey, $data) {
231
        if ($this->isPrimaryKeySet($primaryKey, $data)) {
232
            return true;
233
        } else {
234
            return false;
235
        }
236
    }
237
238 10
    private function runBehaviours($event, $args) {
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...
239 10
        foreach ($this->wrapper->getBehaviours() as $behaviour) {
240
            $args[0] = call_user_func_array([$behaviour, $event], $args);
241
        }
242 10
        return $args[0];
243
    }
244
245
}
246