Completed
Pull Request — devel (#11)
by Philippe
04:21
created

ClientService::findOne()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 31
c 0
b 0
f 0
ccs 22
cts 22
cp 1
rs 8.439
cc 6
eloc 21
nc 3
nop 1
crap 6
1
<?php
2
/**
3
 * ClientService.php
4
 *
5
 * PHP version 5.6+
6
 *
7
 * @author Philippe Gaultier <[email protected]>
8
 * @copyright 2010-2017 Philippe Gaultier
9
 * @license http://www.sweelix.net/license license
10
 * @version 1.1.0
11
 * @link http://www.sweelix.net
12
 * @package sweelix\oauth2\server\services\redis
13
 */
14
15
namespace sweelix\oauth2\server\services\redis;
16
17
use sweelix\oauth2\server\exceptions\DuplicateIndexException;
18
use sweelix\oauth2\server\exceptions\DuplicateKeyException;
19
use sweelix\oauth2\server\interfaces\ClientModelInterface;
20
use sweelix\oauth2\server\interfaces\ClientServiceInterface;
21
use yii\db\Exception as DatabaseException;
22
use Yii;
23
24
/**
25
 * This is the client service for redis
26
 *  database structure
27
 *    * oauth2:clients:<cid> : hash (Client)
28
 *    * oauth2:clients:<cid>:users : set
29
 *    * oauth2:users:<uid>:clients : set
30
 *
31
 * @author Philippe Gaultier <[email protected]>
32
 * @copyright 2010-2017 Philippe Gaultier
33
 * @license http://www.sweelix.net/license license
34
 * @version 1.1.0
35
 * @link http://www.sweelix.net
36
 * @package sweelix\oauth2\server\services\redis
37
 * @since 1.0.0
38
 */
39
class ClientService extends BaseService implements ClientServiceInterface
40
{
41
42
    /**
43
     * @var string user namespace (collection for clients)
44
     */
45
    public $userNamespace = '';
46
47
    /**
48
     * @param string $cid client ID
49
     * @return string client Key
50
     * @since 1.0.0
51
     */
52 21
    protected function getClientKey($cid)
53
    {
54 21
        return $this->namespace . ':' . $cid;
55
    }
56
57
    /**
58
     * @param string $cid client ID
59
     * @return string clientUsers Key
60
     * @since 1.0.0
61
     */
62 6
    protected function getClientUsersListKey($cid)
63
    {
64 6
        return $this->namespace . ':' . $cid . ':users';
65
    }
66
67
    /**
68
     * @param string $uid user ID
69
     * @return string user clients collection Key
70
     * @since XXX
71
     */
72 5
    protected function getUserClientsListKey($uid)
73
    {
74 5
        return $this->userNamespace . ':' . $uid . ':clients';
75
    }
76
77
    /**
78
     * @inheritdoc
79
     */
80 17
    public function save(ClientModelInterface $client, $attributes)
81
    {
82 17
        if ($client->getIsNewRecord()) {
83 17
            $result = $this->insert($client, $attributes);
84 17
        } else {
85 2
            $result = $this->update($client, $attributes);
86
        }
87 17
        return $result;
88
    }
89
90
    /**
91
     * Save Client
92
     * @param ClientModelInterface $client
93
     * @param null|array $attributes attributes to save
94
     * @return bool
95
     * @throws DatabaseException
96
     * @throws DuplicateIndexException
97
     * @throws DuplicateKeyException
98
     * @since 1.0.0
99
     */
100 17
    protected function insert(ClientModelInterface $client, $attributes)
101
    {
102 17
        $result = false;
103 17
        if (!$client->beforeSave(true)) {
104
            return $result;
105
        }
106 17
        $clientKey = $this->getClientKey($client->getKey());
107
        //check if record exists
108 17
        $entityStatus = (int)$this->db->executeCommand('EXISTS', [$clientKey]);
109 17
        if ($entityStatus === 1) {
110 1
            throw new DuplicateKeyException('Duplicate key "'.$clientKey.'"');
111
        }
112
113 17
        $values = $client->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 100 can also be of type array; however, sweelix\oauth2\server\in...e::getDirtyAttributes() does only seem to accept array<integer,string>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
114 17
        $redisParameters = [$clientKey];
115 17
        $this->setAttributesDefinitions($client->attributesDefinition());
116 17
        foreach ($values as $key => $value)
117
        {
118 17
            if ($value !== null) {
119 17
                $redisParameters[] = $key;
120 17
                $redisParameters[] = $this->convertToDatabase($key, $value);
121 17
            }
122 17
        }
123
        //TODO: use EXEC/MULTI to avoid errors
124 17
        $transaction = $this->db->executeCommand('MULTI');
125 17
        if ($transaction === true) {
126
            try {
127 17
                $this->db->executeCommand('HMSET', $redisParameters);
128 17
                $this->db->executeCommand('EXEC');
129 17
            } catch (DatabaseException $e) {
130
                // @codeCoverageIgnoreStart
131
                // we have a REDIS exception, we should not discard
132
                Yii::trace('Error while inserting entity', __METHOD__);
133
                throw $e;
134
                // @codeCoverageIgnoreEnd
135
            }
136 17
        }
137 17
        $changedAttributes = array_fill_keys(array_keys($values), null);
138 17
        $client->setOldAttributes($values);
139 17
        $client->afterSave(true, $changedAttributes);
140 17
        $result = true;
141 17
        return $result;
142
    }
143
144
145
    /**
146
     * Update Client
147
     * @param ClientModelInterface $client
148
     * @param null|array $attributes attributes to save
149
     * @return bool
150
     * @throws DatabaseException
151
     * @throws DuplicateIndexException
152
     * @throws DuplicateKeyException
153
     */
154 2
    protected function update(ClientModelInterface $client, $attributes)
155
    {
156 2
        if (!$client->beforeSave(false)) {
157
            return false;
158
        }
159
160 2
        $values = $client->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 154 can also be of type array; however, sweelix\oauth2\server\in...e::getDirtyAttributes() does only seem to accept array<integer,string>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
161 2
        $modelKey = $client->key();
162 2
        $clientId = isset($values[$modelKey]) ? $values[$modelKey] : $client->getKey();
163 2
        $clientKey = $this->getClientKey($clientId);
164
165
166 2
        if (isset($values[$modelKey]) === true) {
167 1
            $newClientKey = $this->getClientKey($values[$modelKey]);
168 1
            $entityStatus = (int)$this->db->executeCommand('EXISTS', [$newClientKey]);
169 1
            if ($entityStatus === 1) {
170 1
                throw new DuplicateKeyException('Duplicate key "'.$newClientKey.'"');
171
            }
172 1
        }
173
174 2
        $this->db->executeCommand('MULTI');
175
        try {
176 2
            if (array_key_exists($modelKey, $values) === true) {
177 1
                $oldId = $client->getOldKey();
178 1
                $oldClientKey = $this->getClientKey($oldId);
179
180 1
                $this->db->executeCommand('RENAMENX', [$oldClientKey, $clientKey]);
181 1
            }
182
183 2
            $redisUpdateParameters = [$clientKey];
184 2
            $redisDeleteParameters = [$clientKey];
185 2
            $this->setAttributesDefinitions($client->attributesDefinition());
186 2
            foreach ($values as $key => $value)
187
            {
188 2
                if ($value === null) {
189 1
                    $redisDeleteParameters[] = $key;
190 1
                } else {
191 2
                    $redisUpdateParameters[] = $key;
192 2
                    $redisUpdateParameters[] = $this->convertToDatabase($key, $value);
193
                }
194 2
            }
195 2
            if (count($redisDeleteParameters) > 1) {
196 1
                $this->db->executeCommand('HDEL', $redisDeleteParameters);
197 1
            }
198 2
            if (count($redisUpdateParameters) > 1) {
199 2
                $this->db->executeCommand('HMSET', $redisUpdateParameters);
200 2
            }
201
202 2
            $this->db->executeCommand('EXEC');
203 2
        } catch (DatabaseException $e) {
204
            // @codeCoverageIgnoreStart
205
            // we have a REDIS exception, we should not discard
206
            Yii::trace('Error while updating entity', __METHOD__);
207
            throw $e;
208
            // @codeCoverageIgnoreEnd
209
        }
210
211 2
        $changedAttributes = [];
212 2
        foreach ($values as $name => $value) {
213 2
            $oldAttributes = $client->getOldAttributes();
214 2
            $changedAttributes[$name] = isset($oldAttributes[$name]) ? $oldAttributes[$name] : null;
215 2
            $client->setOldAttribute($name, $value);
216 2
        }
217 2
        $client->afterSave(false, $changedAttributes);
218 2
        return true;
219
    }
220
221
    /**
222
     * @inheritdoc
223
     */
224 19
    public function findOne($key)
225
    {
226 19
        $record = null;
227 19
        $clientKey = $this->getClientKey($key);
228 19
        $clientExists = (bool)$this->db->executeCommand('EXISTS', [$clientKey]);
229 19
        if ($clientExists === true) {
230 14
            $clientData = $this->db->executeCommand('HGETALL', [$clientKey]);
231 14
            $record = Yii::createObject('sweelix\oauth2\server\interfaces\ClientModelInterface');
232
            /** @var ClientModelInterface $record */
233 14
            $properties = $record->attributesDefinition();
234 14
            $this->setAttributesDefinitions($properties);
235 14
            $attributes = [];
236 14
            for ($i = 0; $i < count($clientData); $i += 2) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
237 14
                if (isset($properties[$clientData[$i]]) === true) {
238 14
                    $clientData[$i + 1] = $this->convertToModel($clientData[$i], $clientData[($i + 1)]);
239 14
                    $record->setAttribute($clientData[$i], $clientData[$i + 1]);
240 14
                    $attributes[$clientData[$i]] = $clientData[$i + 1];
241
                // @codeCoverageIgnoreStart
242
                } elseif ($record->canSetProperty($clientData[$i])) {
243
                    // TODO: find a way to test attribute population
244
                    $record->{$clientData[$i]} = $clientData[$i + 1];
245
                }
246
                // @codeCoverageIgnoreEnd
247 14
            }
248 14
            if (empty($attributes) === false) {
249 14
                $record->setOldAttributes($attributes);
250 14
            }
251 14
            $record->afterFind();
252 14
        }
253 19
        return $record;
254
    }
255
256
    /**
257
     * @inheritdoc
258
     */
259 1
    public function delete(ClientModelInterface $client)
260
    {
261 1
        $result = false;
262 1
        if ($client->beforeDelete()) {
263 1
            $id = $client->getOldKey();
264 1
            $clientKey = $this->getClientKey($id);
265 1
            $clientUsersListKey = $this->getClientUsersListKey($id);
266 1
            $usersList = $this->db->executeCommand('SMEMBERS', [$clientUsersListKey]);
267
268 1
            $this->db->executeCommand('MULTI');
269
            // remove client from all userClient sets
270 1
            foreach($usersList as $user) {
271
                $userClientKey = $this->getUserClientsListKey($user);
272
                $this->db->executeCommand('SREM', [$userClientKey, $id]);
273 1
            }
274 1
            $this->db->executeCommand('DEL', [$clientKey]);
275 1
            $this->db->executeCommand('DEL', [$clientUsersListKey]);
276
            //TODO: check results to return correct information
277 1
            $queryResult = $this->db->executeCommand('EXEC');
0 ignored issues
show
Unused Code introduced by
$queryResult 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...
278 1
            $client->setIsNewRecord(true);
279 1
            $client->afterDelete();
280 1
            $result = true;
281 1
        }
282 1
        return $result;
283
    }
284
285
    /**
286
     * @inheritdoc
287
     */
288 5
    public function hasUser(ClientModelInterface $client, $userId)
289
    {
290 5
        $key = $client->getKey();
291 5
        $clientUsersListKey = $this->getClientUsersListKey($key);
292 5
        return (bool)$this->db->executeCommand('SISMEMBER', [$clientUsersListKey, $userId]);
293
    }
294
295
    /**
296
     * @inheritdoc
297
     */
298 3
    public function addUser(ClientModelInterface $client, $userId)
299
    {
300 3
        $key = $client->getKey();
301 3
        $clientUsersListKey = $this->getClientUsersListKey($key);
302 3
        $userClientsListKey = $this->getUserClientsListKey($userId);
303 3
        $this->db->executeCommand('MULTI');
304 3
        $this->db->executeCommand('SADD', [$clientUsersListKey, $userId]);
305 3
        $this->db->executeCommand('SADD', [$userClientsListKey, $key]);
306 3
        $queryResult = $this->db->executeCommand('EXEC');
0 ignored issues
show
Unused Code introduced by
$queryResult 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...
307
        //TODO: check if we should send back false or not
308 3
        return true;
309
    }
310
311
    /**
312
     * @inheritdoc
313
     */
314 4
    public function removeUser(ClientModelInterface $client, $userId)
315
    {
316 4
        $key = $client->getKey();
317 4
        $clientUsersListKey = $this->getClientUsersListKey($key);
318 4
        $userClientsListKey = $this->getUserClientsListKey($userId);
319 4
        $this->db->executeCommand('MULTI');
320 4
        $this->db->executeCommand('SREM', [$clientUsersListKey, $userId]);
321 4
        $this->db->executeCommand('SREM', [$userClientsListKey, $key]);
322 4
        $queryResult = $this->db->executeCommand('EXEC');
0 ignored issues
show
Unused Code introduced by
$queryResult 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...
323
        //TODO: check if we should send back false or not
324 4
        return true;
325
    }
326
327
    /**
328
     * @inheritdoc
329
     */
330 1
    public function findAllByUserId($userId)
331
    {
332 1
        $userClientsListKey = $this->getUserClientsListKey($userId);
333 1
        $clientsList = $this->db->executeCommand('SMEMBERS', [$userClientsListKey]);
334 1
        $clients = [];
335 1
        foreach($clientsList as $clientId) {
336 1
            $result = $this->findOne($clientId);
337 1
            if ($result instanceof ClientModelInterface) {
338 1
                $clients[] = $result;
339 1
            }
340 1
        }
341 1
        return $clients;
342
    }
343
344
}
345