ThreemaGateway_Model_Messages   C
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 648
Duplicated Lines 3.09 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 59
lcom 1
cbo 0
dl 20
loc 648
rs 5.6989
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A preQuery() 0 5 1
A injectFetchOption() 0 8 2
A resetFetchOptions() 0 7 1
A setMessageId() 0 7 2
A setSenderId() 0 7 1
A setTypeCode() 0 7 1
A setKeyword() 0 5 1
A setTimeLimit() 0 11 3
A setResultLimit() 0 4 1
A setOrder() 0 5 1
B getAllMessageData() 0 58 8
D getMessageDataByType() 20 140 13
C getMessageMetaData() 0 37 8
A removeMetaData() 0 21 2
A getMessageIdsFromResult() 0 15 3
A pushArrayKeys() 0 21 4
A groupArray() 0 14 3
A appendMixedCondition() 0 15 4

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ThreemaGateway_Model_Messages often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ThreemaGateway_Model_Messages, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Model for messages stored in database.
4
 *
5
 * @package ThreemaGateway
6
 * @author rugk
7
 * @copyright Copyright (c) 2015-2016 rugk
8
 * @license MIT
9
 */
10
11
class ThreemaGateway_Model_Messages extends XenForo_Model
12
{
13
    /**
14
     * @var string database table (prefix) for messages
15
     */
16
    const DB_TABLE_MESSAGES = 'xf_threemagw_messages';
17
18
    /**
19
     * @var string database table for files
20
     */
21
    const DB_TABLE_FILES = 'xf_threemagw_files';
22
23
    /**
24
     * @var string database table for acknowledged messages/delivery receipts
25
     */
26
    const DB_TABLE_DELIVERY_RECEIPT = 'xf_threemagw_ackmsgs';
27
28
    /**
29
     * @var int constant for type code
30
     */
31
    const TYPE_DELIVERY_MESSAGE = 0x80;
32
33
    /**
34
     * @var int constant for type code
35
     */
36
    const TYPE_FILE_MESSAGE = 0x17;
37
38
    /**
39
     * @var int constant for type code
40
     */
41
    const TYPE_IMAGE_MESSAGE = 0x02;
42
43
    /**
44
     * @var int constant for type code
45
     */
46
    const TYPE_TEXT_MESSAGE = 0x01;
47
48
    /**
49
     * @var array constant for type code
50
     */
51
    const ORDER_CHOICE = [
52
        'id' => 'message_id',
53
        'date_send' => 'date_send',
54
        'date_received' => 'date_received',
55
        'delivery_state' => 'message.receipt_type',
56
    ];
57
58
    /**
59
     * @var array data used when querying
60
     */
61
    protected $fetchOptions = [
62
        'where' => [],
63
        'params' => []
64
    ];
65
66
    /**
67
     * Execute this before any query.
68
     *
69
     * Sets internal values necessary for a correct connection to the database.
70
     */
71
    public function preQuery()
72
    {
73
        // set correct character encoding
74
        $this->_getDb()->query('SET NAMES utf8mb4');
75
    }
76
77
    /**
78
     * Inject or modify a fetch option manually.
79
     *
80
     * Sets internal values necessary for a correct connection to the database.
81
     * This should best be avoided, when it is not really necessary to change
82
     * the value directly.
83
     * It can e.g. be used to reset data. When you e.g. want to reset the where
84
     * option call it this way: injectFetchOption('where', []).
85
     *
86
     * @param string $option The option name to inject
87
     * @param string $value  The value of the option to set.
88
     * @param bool   $append If set to true, the value is not overriden, but
89
     *                       just appended as an array. (default: false)
90
     */
91
    public function injectFetchOption($option, $value, $append = false)
92
    {
93
        if ($append) {
94
            $this->fetchOptions[$option][] = $value;
95
        } else {
96
            $this->fetchOptions[$option] = $value;
97
        }
98
    }
99
100
    /**
101
     * Rests all fetch options. This is useful to prevent incorrect or
102
     * unexpected results when using one model for multiple queries (not
103
     * recommend) or for e.g. resetting the options before calling
104
     * {@link fetchAll()};.
105
     */
106
    public function resetFetchOptions()
107
    {
108
        $this->fetchOptions = [];
109
        // set empty data, which is required to prevent failing
110
        $this->fetchOptions['where']  = [];
111
        $this->fetchOptions['params'] = [];
112
    }
113
114
    /**
115
     * Sets the message ID(s) for the query.
116
     *
117
     * @param string|array $messageIds  one (string) or more (array) message IDs
118
     * @param string       $tablePrefix The table prefix (optional)
119
     */
120
    public function setMessageId($messageIds, $tablePrefix = null)
121
    {
122
        return $this->appendMixedCondition(
123
            ($tablePrefix ? $tablePrefix . '.' : '') . 'message_id',
124
            $messageIds
125
        );
126
    }
127
128
    /**
129
     * Sets the sender Threema ID(s) for querying it/them.
130
     *
131
     * @param string $threemaIds one (string) or more (array) Threema IDs
132
     */
133
    public function setSenderId($threemaIds)
134
    {
135
        return $this->appendMixedCondition(
136
            'metamessage.sender_threema_id',
137
            $threemaIds
138
        );
139
    }
140
141
    /**
142
     * Sets the type code(s) for querying only one (or a few) type.
143
     *
144
     * Please use the TYPE_* constants for specifying the type code(s).
145
     * You should avoid using this and rather use {@link getMessageDataByType()}
146
     * directly if you know the type code.
147
     * If you want to limit the types you want to query this method would be a
148
     * good way for you to use.
149
     *
150
     * @param string $typeCodes one (string) or more (array) type codes(s)
151
     */
152
    public function setTypeCode($typeCodes)
153
    {
154
        return $this->appendMixedCondition(
155
            'metamessage.message_type_code',
156
            $typeCodes
157
        );
158
    }
159
160
    /**
161
     * Sets a string to look for when querying text messages.
162
     *
163
     * The string is processed by MySQL via the `LIKE` command and may
164
     * therefore contain some wildcards: % for none or any character and
165
     * _ for exactly one character.
166
     * Attention: This is only possible when using the text message type!
167
     * Otherwise your query will fail.
168
     *
169
     * @param string $keyword a keyword to look for
170
     */
171
    public function setKeyword($keyword)
172
    {
173
        $this->fetchOptions['where'][]  = 'message.text LIKE ?';
174
        $this->fetchOptions['params'][] = $keyword;
175
    }
176
177
    /**
178
     * Sets the a time limit for what messages should be queried.
179
     *
180
     * @param int|null $dateMin   oldest date of messages (optional)
181
     * @param int|null $dateMax   latest date of messages (optional)
182
     * @param string   $attribute Set the atttribute to apply this to.
183
     */
184
    public function setTimeLimit($dateMin = null, $dateMax = null, $attribute = 'metamessage.date_send')
185
    {
186
        if ($dateMin) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dateMin of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
187
            $this->fetchOptions['where'][]  = $attribute . ' >= ?';
188
            $this->fetchOptions['params'][] = $dateMin;
189
        }
190
        if ($dateMax) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dateMax of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
191
            $this->fetchOptions['where'][]  = $attribute . ' <= ?';
192
            $this->fetchOptions['params'][] = $dateMax;
193
        }
194
    }
195
196
    /**
197
     * Limit the result to a number of datasets.
198
     *
199
     * @param int $limit oldest date of messages
200
     */
201
    public function setResultLimit($limit)
202
    {
203
        $this->fetchOptions['limit'] = $limit;
204
    }
205
206
    /**
207
     * Sets an order for the query.
208
     *
209
     * This function overwrites previous values if they were set as ordering by
210
     * multiple columns is not possible.
211
     *
212
     * @param int    $column    the column to order by (see {@link OrderChoice} for valid values)
213
     * @param string $direction asc or desc
214
     */
215
    public function setOrder($column, $direction = 'asc')
216
    {
217
        $this->fetchOptions['order']     = $column;
218
        $this->fetchOptions['direction'] = $direction;
219
    }
220
221
    /**
222
     * Queries all available data from a list of message IDs.
223
     *
224
     * Note that this requires one to have the meta data of the messages already
225
     * and therefore you have to run {@link getMessageMetaData()} before and
226
     * submit it as the first parameter.
227
     * This method also resets the conditions of the where clause
228
     * ($fetchOptions['where']) and the params ($fetchOptions['params']) based
229
     * on the results included in the meta data. Other fetch options however
230
     * remain and are still applied, so if you want to avoid this, use
231
     * {@link resetFetchOptions()}.
232
     * Note that the ordering values of different message types will not work as
233
     * this function internally needs to handle each message type differently.
234
     *
235
     * @param  array[string]     $metaData           The message meta data from
0 ignored issues
show
Documentation introduced by
The doc-type array[string] could not be parsed: Expected "]" at position 2, but found "string". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
236
     *                                               {@link getMessageMetaData()}
237
     *                                               (without grouping)
238
     * @param  bool              $groupByMessageType Set to true to group the
239
     *                                               return value via message
240
     *                                               types. (default: false)
241
     * @throws XenForo_Exception
242
     * @return null|array
243
     */
244
    public function getAllMessageData(array $metaData, $groupByMessageType = false)
245
    {
246
        // get grouped messages by type
247
        $messageTypes = $this->groupArray($metaData, 'message_type_code');
248
        // we always need to do this (regardless of message_type_code) as each
249
        // message type needs to be handled individually
250
251
        // query message types individually
252
        $output = null;
253
        foreach ($messageTypes as $messageType => $messages) {
254
            // get messages of current data type in groups
255
            $groupedMessages = $this->groupArray($messages, 'message_id', true);
256
257
            // overwrite conditions with message IDs we already know
258
            $this->fetchOptions['params'] = [];
259
            $this->fetchOptions['where']  = [];
260
            $this->setMessageId($this->getMessageIdsFromResult($messages), 'message');
261
262
            // query data
263
            $groupedResult = $this->getMessageDataByType($messageType, false);
264
            // skip processing if there are no results (most likely all
265
            // messages of this type have been deleted)
266
            if (!is_array($groupedResult)) {
267
                continue;
268
            }
269
270
            // go through each message to merge result with meta data
271
            foreach ($groupedMessages as $msgId => $msgMetaData) {
272
                // ignore non-exisiting key (might be deleted messages)
273
                if (!array_key_exists($msgId, $groupedResult)) {
274
                    continue;
275
                }
276
277
                // merge arrays
278
                $mergedArrays = $msgMetaData + $groupedResult[$msgId];
279
280
                // remove unnecessary message_id (the ID is already the key)
281
                if (array_key_exists('message_id', $mergedArrays)) {
282
                    unset($mergedArrays['message_id']);
283
                }
284
285
                // save as output
286
                if ($groupByMessageType) {
287
                    // remove unnecessary message_type_code (as it is already
288
                    // grouped by it)
289
                    if (array_key_exists('message_type_code', $mergedArrays)) {
290
                        unset($mergedArrays['message_type_code']);
291
                    }
292
293
                    $output[$messageType][$msgId] = $mergedArrays;
294
                } else {
295
                    $output[$msgId] = $mergedArrays;
296
                }
297
            }
298
        }
299
300
        return $output;
301
    }
302
303
    /**
304
     * Queries all available data for a message type.
305
     *
306
     * The return value should be an array in the same format as the one
307
     * returned by {@link getAllMessageData()} when $groupByMessageType is set
308
     * to false. Of course, however, only one message type is returned here.
309
     *
310
     * @param  int               $messageType     The message type the messages belong to
311
     * @param  bool              $includeMetaData Set to true to also include the main
312
     *                                            message table in your query. If you do so you
313
     *                                            will also get the meta data of the message.
314
     *                                            (default: true)
315
     * @throws XenForo_Exception
316
     * @return null|array
317
     */
318
    public function getMessageDataByType($messageType, $includeMetaData = true)
319
    {
320
        /** @var array $output */
321
        $output = [];
322
323
        // prepare query
324
        /** @var array $limitOptions */
325
        $limitOptions = $this->prepareLimitFetchOptions($this->fetchOptions);
326
327
        // built query
328
        switch ($messageType) {
329
            case self::TYPE_DELIVERY_MESSAGE:
330
                /** @var Zend_Db_Table_Select $select */
331
                $select = $this->_getDb()->select()
332
                    ->from(['message' => self::DB_TABLE_MESSAGES . '_delivery_receipt'])
333
                    ->joinInner(
334
                        ['ack_messages' => self::DB_TABLE_DELIVERY_RECEIPT],
335
                        'message.message_id = ack_messages.message_id'
336
                    );
337
338
                /** @var string $resultIndex index to use for additional data from query */
339
                $resultIndex = 'ackmsgs';
340
                break;
341
342 View Code Duplication
            case self::TYPE_FILE_MESSAGE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
343
                /** @var Zend_Db_Table_Select $select */
344
                $select = $this->_getDb()->select()
345
                    ->from(['message' => self::DB_TABLE_MESSAGES . '_file'])
346
                    ->joinInner(
347
                        ['filelist' => self::DB_TABLE_FILES],
348
                        'filelist.message_id = message.message_id'
349
                    );
350
351
                /** @var string $resultIndex index to use for additional data from query */
352
                $resultIndex = 'files';
353
                break;
354
355 View Code Duplication
            case self::TYPE_IMAGE_MESSAGE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
356
                /** @var Zend_Db_Table_Select $select */
357
                $select = $this->_getDb()->select()
358
                    ->from(['message' => self::DB_TABLE_MESSAGES . '_image'])
359
                    ->joinInner(
360
                        ['filelist' => self::DB_TABLE_FILES],
361
                        'filelist.message_id = message.message_id'
362
                    );
363
364
                /** @var string $resultIndex index to use for additional data from query */
365
                $resultIndex = 'files';
366
                break;
367
368
            case self::TYPE_TEXT_MESSAGE:
369
                /** @var Zend_Db_Table_Select $select */
370
                $select = $this->_getDb()->select()
371
                    ->from(['message' => self::DB_TABLE_MESSAGES . '_text']);
372
373
                // although this is not strictly necessary, to ease the
374
                // processing the data later, we also index this
375
                /** @var string $resultIndex index to use for additional data from query */
376
                $resultIndex = 'text';
377
                break;
378
379
            default:
380
                throw new XenForo_Exception(new XenForo_Phrase('threemagw_unknown_message_type'));
381
        }
382
383
        // add table if necessary
384
        if ($includeMetaData) {
385
            $select->joinInner(
386
                ['metamessage' => self::DB_TABLE_MESSAGES],
387
                'message.message_id = metamessage.message_id'
388
            );
389
        }
390
391
        // general options for query
392
        $select
393
            ->where($this->getConditionsForClause($this->fetchOptions['where']))
394
            ->order($this->getOrderByClause(self::ORDER_CHOICE, $this->fetchOptions));
395
396
        // execute query
397
        /** @var array|null $result database query result */
398
        $result = $this->_getDb()->fetchAll(
399
            $this->limitQueryResults(
400
                $select,
401
                $limitOptions['limit'], $limitOptions['offset']),
402
        $this->fetchOptions['params']);
403
404
        // throw error if data is missing
405
        if (!is_array($result)) {
406
            throw new XenForo_Exception(new XenForo_Phrase('threemagw_missing_database_data'));
407
        }
408
        // if there is no result, just return null
409
        if (empty($result)) {
410
            return null;
411
        }
412
413
        // group array by message ID
414
        $result = $this->groupArray($result, 'message_id');
415
416
        // attributes to remove/push
417
        $removeAttributes = [
418
            'message_id',
419
            'file_name',
420
            'mime_type',
421
            'file_size'
422
        ];
423
        if ($includeMetaData) {
424
            $removeAttributes = array_merge($removeAttributes, [
425
                'message_type_code',
426
                'sender_threema_id',
427
                'date_send',
428
                'date_received'
429
            ]);
430
        }
431
432
        // push general attributes one array up
433
        if (!$resultIndex) {
434
            throw new XenForo_Exception(new XenForo_Phrase('threemagw_unknown_message_type'));
435
            break;
436
        }
437
438
        // go through each message
439
        foreach ($result as $msgId => $resultForId) {
440
            $output[$msgId] = [];
441
            $output[$msgId] = $this->pushArrayKeys($output[$msgId],
442
                                    $resultForId,
443
                                    $removeAttributes);
444
            $output[$msgId][$resultIndex] = $resultForId;
445
446
            // remove unnecessary message_id (the ID is already the key)
447
            if (array_key_exists('message_id', $output[$msgId])) {
448
                unset($output[$msgId]['message_id']);
449
            }
450
        }
451
452
        if (!$output) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $output of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
453
            return null;
454
        }
455
456
        return $output;
457
    }
458
459
    /**
460
     * Returns only the meta data of one or more messages not depending on the
461
     * type of the message.
462
     *
463
     * @param  bool       $groupById     When true groups the data by the message ID (default: false)
464
     * @param  bool       $ignoreInvalid When true removes data sets where the message content may be deleted (default: true)
465
     * @return null|array
466
     */
467
    public function getMessageMetaData($groupById = false, $ignoreInvalid = true)
468
    {
469
        /** @var array $limitOptions */
470
        $limitOptions = $this->prepareLimitFetchOptions($this->fetchOptions);
471
472
        /** @var array $result query result */
473
        $result = $this->_getDb()->fetchAll(
474
            $this->limitQueryResults(
475
                $this->_getDb()->select()
476
                    ->from(['metamessage' => self::DB_TABLE_MESSAGES])
477
                    ->where($this->getConditionsForClause($this->fetchOptions['where']))
478
                    ->order($this->getOrderByClause(self::ORDER_CHOICE, $this->fetchOptions)),
479
            $limitOptions['limit'], $limitOptions['offset']),
480
        $this->fetchOptions['params']);
481
482
        // fail if there is no data
483
        if (!is_array($result) || !$result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
484
            return null;
485
        }
486
487
        // remove invalid data sets (where message might be deleted)
488
        if ($ignoreInvalid) {
489
            foreach ($result as $i => $msgData) {
490
                if (!array_key_exists('message_type_code', $msgData) ||
491
                    !$msgData['message_type_code']) {
492
                    unset($result[$i]);
493
                }
494
            }
495
        }
496
497
        // group array by message ID if wanted
498
        if ($groupById) {
499
            $result = $this->groupArray($result, 'message_id');
500
        }
501
502
        return $result;
503
    }
504
505
    /**
506
     * Removes entries or meta data fields from the meta data message table.
507
     *
508
     * Note that if the message(s) has/have other data saved in the database
509
     * the full deletion will fail.
510
     * Note: The number of where and params-options must be equal. You can
511
     * submit additional conditions with the first parameter.
512
     * Attention: This ignores the limit/offset clause for simplicity.
513
     *
514
     * @param string[] $additionalConditions Add additional where conditions if
515
     *                                       neccessary.
516
     * @param string[] $removeOnlyField      When set only the passed fields are
517
     *                                       updated to "null" rather than deleting
518
     *                                       the whole record.
519
     */
520
    public function removeMetaData(array $additionalConditions = [], array $removeOnlyField = [])
521
    {
522
        if (!empty($removeOnlyField)) {
523
            $this->_getDb()->update(
524
                self::DB_TABLE_MESSAGES,
525
                array_fill_keys($removeOnlyField, null),
526
                array_merge(
527
                    array_combine($this->fetchOptions['where'], $this->fetchOptions['params']),
528
                    $additionalConditions
529
                )
530
            );
531
        } else {
532
            $this->_getDb()->delete(
533
                self::DB_TABLE_MESSAGES,
534
                array_merge(
535
                    array_combine($this->fetchOptions['where'], $this->fetchOptions['params']),
536
                    $additionalConditions
537
                )
538
            );
539
        }
540
    }
541
542
    /**
543
     * Returns all available data from a list of message IDs.
544
     *
545
     * @param  array[string]     $messages The message result
0 ignored issues
show
Documentation introduced by
The doc-type array[string] could not be parsed: Expected "]" at position 2, but found "string". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
546
     * @throws XenForo_Exception
547
     * @return null|array
548
     */
549
    protected function getMessageIdsFromResult(array $messages)
550
    {
551
        // use PHP function if available (>= PHP 5.5.0)
552
        if (function_exists('array_column')) {
553
            return array_column($messages, 'message_id');
554
        }
555
556
        // manually extract message_id from array
557
        $output = [];
558
        foreach ($messages as $message) {
559
            $output[] = $message['message_id'];
560
        }
561
562
        return $output;
563
    }
564
565
    /**
566
     * Removes the specified keys from the second array and pushes them into
567
     * the first base array.
568
     * The subarray must be indexed by integers, where each index contains an
569
     * associative array with the keys to remove.
570
     * It assumes that the 0-index of $subArray is there, including the data,
571
     * which should be pushed to $baseArray.
572
     *
573
     * @param array    $baseArray  the main array, where the key/value pairs get to
574
     * @param array    $subArray   the array, which keys should be removed
575
     * @param string[] $removeKeys an array of keys, which should be removed
576
     *
577
     * @throws XenForo_Exception
578
     * @return false|array
579
     */
580
    protected function pushArrayKeys(array &$baseArray, array &$subArray, array $removeKeys)
581
    {
582
        foreach ($removeKeys as $key) {
583
            // skip invalid keys
584
            if (!array_key_exists($key, $subArray[0])) {
585
                continue;
586
            }
587
588
            // move value from subarray to base array
589
            $baseArray[$key] = $subArray[0][$key];
590
591
            // then delete it from sub array
592
            /** @var int $subArrayCount */
593
            $subArrayCount = count($subArray);
594
            for ($i = 0; $i < $subArrayCount; $i++) {
595
                unset($subArray[$i][$key]);
596
            }
597
        }
598
599
        return $baseArray;
600
    }
601
602
    /**
603
     * Groups an array by using the value of a specific index in it.
604
     *
605
     * @param array      $array       the array, which is sued as the base
606
     * @param string|int $indexKey    the value of the key, which should be used
607
     *                                for indexing
608
     * @param bool       $ignoreIndex Set to true to ignore multiple values in
609
     *                                $array. If activated only the last key of
610
     *                                $array will be placed into the group and
611
     *                                it will be the only key. This is only
612
     *                                useful if you know for sure that only one
613
     *                                key is available.
614
     *
615
     * @return array
616
     */
617
    public function groupArray(array $array, $indexKey, $ignoreIndex = false)
618
    {
619
        /** @var array $output */
620
        $output = [];
621
        foreach ($array as $i => $value) {
622
            if ($ignoreIndex) {
623
                $output[$value[$indexKey]] = $value;
624
            } else {
625
                $output[$value[$indexKey]][] = $value;
626
            }
627
        }
628
629
        return $output;
630
    }
631
632
    /**
633
     * Appends a WHERE condition for either a string or an array.
634
     *
635
     * It automatically chooses between a simple `this = ?` or a more complex
636
     * `this IN (?, ?, ...)`.
637
     *
638
     * @param string       $attName  the name of the required attribut
639
     * @param string|array $attValue the value, which should be required
640
     *
641
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null?

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...
642
     */
643
    protected function appendMixedCondition($attName, $attValue)
644
    {
645
        // convert arrays with only one value
646
        if (is_array($attValue) && count($attValue) == 1) {
647
            $attValue = $attValue[0];
648
        }
649
650
        if (!is_array($attValue)) {
651
            $this->fetchOptions['where'][]  = $attName . ' = ?';
652
            $this->fetchOptions['params'][] = $attValue;
653
        } else {
654
            $this->fetchOptions['where'][]  = $attName . ' IN (' . implode(', ', array_fill(0, count($attValue), '?')) . ')';
655
            $this->fetchOptions['params'] += $attValue;
656
        }
657
    }
658
}
659