Completed
Push — master ( ad83ed...4c8d32 )
by James Ekow Abaka
03:09
created

DataOperations::isPrimaryKeySet()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 7.392

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 18
ccs 8
cts 10
cp 0.8
rs 8.2222
cc 7
eloc 10
nc 9
nop 2
crap 7.392
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
use ntentan\utils\Utils;
30
use ntentan\atiaa\Db;
31
use ntentan\panie\InjectionContainer;
32
33
/**
34
 * Description of DataOperations
35
 *
36
 * @author ekow
37
 */
38
class DataOperations
39
{
40
    private $wrapper;
41
    
42
    /**
43
     *
44
     * @var DriverAdapter
45
     */
46
    private $adapter;
47
    private $data;
48
    private $invalidFields;
49
    private $hasMultipleData;
50
    
51
    const MODE_SAVE = 0;
52
    const MODE_UPDATE = 1;
53
    
54 34
    public function __construct($wrapper, $adapter)
55
    {
56 34
        $this->wrapper = $wrapper;
57 34
        $this->adapter = $adapter;        
58 34
    }
59
    
60 10
    public function doSave($hasMultipleData)
61
    {
62 10
        $this->hasMultipleData = $hasMultipleData;
63 10
        $invalidFields = [];
64 10
        $data = $this->wrapper->getData();
65 10
        $this->adapter->setModel($this->wrapper);
66 10
        $primaryKey = $this->wrapper->getDescription()->getPrimaryKey();
67 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...
68 10
        $succesful = true;
69
70 10
        if (count($primaryKey) == 1) {
71 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...
72
        }
73
        
74
        // Assign an empty array to force a validation error for empty models
75 10
        if(empty($data)) {
76 2
            $data = [[]];
77
        }
78
79 10
        Db::getDriver()->beginTransaction();
80
81 10
        foreach($data as $i => $datum) {
82 10
            $status = $this->saveRecord($datum, $primaryKey);
83
            $data[$i] = $datum;
84
            
85
            if(!$status['success']) {
86
                $succesful = false;
87
                $invalidFields[$i] = $status['invalid_fields'];
88
                Db::getDriver()->rollback();
89
                break;
90
            }
91
        }
92
        
93
        if($succesful) {
94
            Db::getDriver()->commit();
95
        } else {
96
            $this->assignValue($this->invalidFields, $invalidFields);
97
        }
98
        
99
        $this->wrapper->setData($hasMultipleData ? $data : $data[0]);
100
101
        return $succesful;
102
    }   
103
    
104
    /**
105
     * Save an individual record.
106
     * 
107
     * @param array $record The record to be saved
108
     * @param type $primaryKey The primary keys of the record
109
     * @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...
110
     */
111 10
    private function saveRecord(&$record, $primaryKey)
112
    {
113
        $status = [
114 10
            'success' => true,
115
            'pk_assigned' => null,
116
            'invalid_fields' => []
117
        ];
118
        
119
        // Determine if the primary key of the record is set.
120 10
        $pkSet = $this->isPrimaryKeySet($primaryKey, $record);
121
        
122
        // Reset the data in the model to contain only the data to be saved
123 10
        $this->wrapper->setData($record);
124
125
        // Run preUpdate or preSave callbacks on models and behaviours
126 10
        if($pkSet) {
127 2
            $this->wrapper->preUpdateCallback();
128 2
            $record = $this->wrapper->getData();
129 2
            $record = reset($record) === false ? [] : reset($record);
130 2
            $record = $this->runBehaviours('preUpdateCallback', [$record]);
131
        } else {
132 8
            $this->wrapper->preSaveCallback();
133 8
            $record = $this->wrapper->getData();
134 8
            $record = reset($record) === false ? [] : reset($record);
135 8
            $record = $this->runBehaviours('preSaveCallback', [$record]);
136
        }        
137
        
138
        // Validat the data
139 10
        $validity = $this->validate(
140
            $record, 
141 10
            $pkSet ? DataOperations::MODE_UPDATE : DataOperations::MODE_SAVE
142
        );
143
144
        // Exit if data is invalid
145
        if($validity !== true) {
146
            $status['invalid_fields'] = $validity;
147
            $status['success'] = false;
148
            return $status;
149
        }
150
        
151
        // Assign the data to the wrapper again
152
        $this->wrapper->setData($record);
153
154
        // Update or save the data and run post callbacks
155
        if($pkSet) {
156
            $this->adapter->update($record);
157
            $this->wrapper->postUpdateCallback();
158
            $this->runBehaviours('postUpdateCallback', [$record]);
159
        } else {
160
            $this->adapter->insert($record);
161
            $keyValue = Db::getDriver()->getLastInsertId();
162
            $this->wrapper->{$primaryKey[0]} = $keyValue;
163
            $this->wrapper->postSaveCallback($keyValue);
164
            $this->runBehaviours('postSaveCallback', [$record, $keyValue]);
165
        }
166
        
167
        // Reset the data so it contains any modifications made by callbacks
168
        $record = $this->wrapper->getData()[0];
169
        return $status;
170
    }
171
    
172 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...
173
    {
174 10
        $valid = true;
175 10
        $validator = \ntentan\panie\InjectionContainer::singleton(
176 10
            ModelValidator::class,
177 10
            ['model' => $this->wrapper, 'mode' => $mode]
0 ignored issues
show
Unused Code introduced by
The call to InjectionContainer::singleton() has too many arguments starting with array('model' => $this->wrapper, 'mode' => $mode).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
178
        );
179
180
        if(!$validator->validate($data)) {
181
            $valid = false;
182
        }
183
184
        if($valid === false) {
185
            $valid = $validator->getInvalidFields();
186
        }
187
188
        return $valid;
189
    }
190
    
191
192 10
    private function isPrimaryKeySet($primaryKey, $data)
193
    {
194 10
        if(is_string($primaryKey)) {
195
            if(isset($data[$primaryKey]))
196
            {
197
                return true;
198
            }
199
        }
200 10
        foreach($primaryKey as $keyField) {
201 10
            if(!isset($data[$keyField])) {
202 8
                break;
203
            }
204 2
            if($data[$keyField] !== '' && $data[$keyField] !== null) {
205 2
                return true;
206
            }
207
        }
208 8
        return false;
209
    }
210
    
211
    private function assignValue(&$property, $value)
212
    {
213
        if($this->hasMultipleData) {
214
            $property = $value;
215
        } else {
216
            $property = $value[0];
217
        }
218
    }    
219
    
220
    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...
221
    {
222
        return $this->data;
223
    }
224
    
225
    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...
226
    {
227
        return $this->invalidFields;
228
    }   
229
    
230
    public function isItemDeletable($primaryKey, $data)
231
    {
232
        if($this->isPrimaryKeySet($primaryKey, $data)) {
233
            return true;
234
        } else {
235
            return false;
236
        }
237
    }     
238
    
239 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...
240
    {
241 10
        foreach($this->wrapper->getBehaviours() as $behaviour) {
242
            $args[0] = call_user_func_array([$behaviour, $event], $args);
243
        }
244 10
        return $args[0];
245
    }
246
}
247