BuildsUpdates::updateOrInsert()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 1
b 0
f 0
nc 6
nop 2
dl 0
loc 18
rs 9.9666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent\Query\Concerns;
6
7
use Closure;
8
use Illuminate\Database\Query\Expression;
9
use Illuminate\Support\Arr;
10
use InvalidArgumentException;
11
use LaravelFreelancerNL\Aranguent\Query\Grammar;
12
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException;
13
14
/**
15
 * @method applyBeforeQueryCallbacks()
16
 */
17
trait BuildsUpdates
18
{
19
    /**
20
     * @param array<mixed> $values
21
     * @return array<mixed>
22
     */
23
    protected function prepareValuesForUpdate(array $values)
24
    {
25
        foreach ($values as $key => $value) {
26
            if ($value instanceof Expression) {
27
                $values[$key] = $value->getValue($this->grammar);
28
29
                continue;
30
            }
31
32
            if (is_array($value)) {
33
                $values[$key] = $this->prepareValuesForUpdate($value);
34
                continue;
35
            }
36
37
            $values[$key]  = $this->bindValue($value, 'update');
0 ignored issues
show
Bug introduced by
It seems like bindValue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

37
            /** @scrutinizer ignore-call */ 
38
            $values[$key]  = $this->bindValue($value, 'update');
Loading history...
38
        }
39
40
        return $values;
41
    }
42
43
    /**
44
     * Update records in the database.
45
     *
46
     * @param  array<mixed>  $values
47
     * @return int
48
     */
49
    public function update(array $values)
50
    {
51
        assert($this->grammar instanceof Grammar);
52
53
        $this->applyBeforeQueryCallbacks();
54
55
        $values = Arr::undot($this->grammar->convertJsonFields($values));
56
57
        $values = $this->prepareValuesForUpdate($values);
58
59
        $aql = $this->grammar->compileUpdate($this, $values);
0 ignored issues
show
Bug introduced by
$this of type LaravelFreelancerNL\Aran...\Concerns\BuildsUpdates is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $query of LaravelFreelancerNL\Aran...rammar::compileUpdate(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

59
        $aql = $this->grammar->compileUpdate(/** @scrutinizer ignore-type */ $this, $values);
Loading history...
60
61
        return $this->connection->update($aql, $this->getBindings());
0 ignored issues
show
Bug introduced by
It seems like getBindings() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

61
        return $this->connection->update($aql, $this->/** @scrutinizer ignore-call */ getBindings());
Loading history...
62
    }
63
64
    /**
65
     * Insert or update a record matching the attributes, and fill it with values.
66
     *
67
     * @param array<mixed> $attributes
68
     * @param array<mixed>|callable $values
69
     * @return bool
70
     * @throws BindException
71
     */
72
    public function updateOrInsert(array $attributes, array|callable $values = [])
73
    {
74
        $exists = $this->where($attributes)->exists();
0 ignored issues
show
Bug introduced by
It seems like where() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

74
        $exists = $this->/** @scrutinizer ignore-call */ where($attributes)->exists();
Loading history...
75
76
        if ($values instanceof Closure) {
0 ignored issues
show
introduced by
$values is never a sub-type of Closure.
Loading history...
77
            $values = $values($exists);
78
        }
79
80
        if (! $exists) {
81
            $this->bindings['where'] = [];
0 ignored issues
show
Bug Best Practice introduced by
The property bindings does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
82
            return $this->insert(array_merge($attributes, $values));
0 ignored issues
show
Bug introduced by
It seems like insert() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

82
            return $this->/** @scrutinizer ignore-call */ insert(array_merge($attributes, $values));
Loading history...
83
        }
84
85
        if (empty($values)) {
86
            return true;
87
        }
88
89
        return (bool) $this->limit(1)->update($values);
0 ignored issues
show
Bug introduced by
It seems like limit() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

89
        return (bool) $this->/** @scrutinizer ignore-call */ limit(1)->update($values);
Loading history...
90
    }
91
92
    /**
93
     * Increment the given column's values by the given amounts.
94
     *
95
     * @param  array<string, float|int|numeric-string>  $columns
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, float|int|numeric-string> at position 8 could not be parsed: Unknown type name 'numeric-string' at position 8 in array<string, float|int|numeric-string>.
Loading history...
96
     * @param  array<string, mixed>  $extra
97
     * @return int
98
     *
99
     * @throws \InvalidArgumentException
100
     */
101
    public function incrementEach(array $columns, array $extra = [])
102
    {
103
        foreach ($columns as $column => $amount) {
104
            if (!is_numeric($amount)) {
105
                throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '$column'.");
106
            } elseif (!is_string($column)) {
107
                throw new InvalidArgumentException('Non-associative array passed to incrementEach method.');
108
            }
109
110
            $columns[$column] = new Expression($this->getTableAlias($this->from) . '.' . $column . ' + ' . $amount);
0 ignored issues
show
Bug introduced by
It seems like getTableAlias() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

110
            $columns[$column] = new Expression($this->/** @scrutinizer ignore-call */ getTableAlias($this->from) . '.' . $column . ' + ' . $amount);
Loading history...
Bug introduced by
$this->getTableAlias($th...olumn . ' + ' . $amount of type string is incompatible with the type Illuminate\Database\Query\TValue expected by parameter $value of Illuminate\Database\Quer...pression::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

110
            $columns[$column] = new Expression(/** @scrutinizer ignore-type */ $this->getTableAlias($this->from) . '.' . $column . ' + ' . $amount);
Loading history...
111
        }
112
113
        return $this->update(array_merge($columns, $extra));
114
    }
115
116
    /**
117
     * Decrement the given column's values by the given amounts.
118
     *
119
     * @param  array<string, float|int|numeric-string>  $columns
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, float|int|numeric-string> at position 8 could not be parsed: Unknown type name 'numeric-string' at position 8 in array<string, float|int|numeric-string>.
Loading history...
120
     * @param  array<string, mixed>  $extra
121
     * @return int
122
     *
123
     * @throws \InvalidArgumentException
124
     */
125
    public function decrementEach(array $columns, array $extra = [])
126
    {
127
        foreach ($columns as $column => $amount) {
128
            if (!is_numeric($amount)) {
129
                throw new InvalidArgumentException("Non-numeric value passed as decrement amount for column: '$column'.");
130
            } elseif (!is_string($column)) {
131
                throw new InvalidArgumentException('Non-associative array passed to decrementEach method.');
132
            }
133
134
            $columns[$column] = new Expression($this->getTableAlias($this->from) . '.' . $column . ' - ' . $amount);
0 ignored issues
show
Bug introduced by
$this->getTableAlias($th...olumn . ' - ' . $amount of type string is incompatible with the type Illuminate\Database\Query\TValue expected by parameter $value of Illuminate\Database\Quer...pression::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

134
            $columns[$column] = new Expression(/** @scrutinizer ignore-type */ $this->getTableAlias($this->from) . '.' . $column . ' - ' . $amount);
Loading history...
135
        }
136
137
        return $this->update(array_merge($columns, $extra));
138
    }
139
140
    /**
141
     * Insert new records or update the existing ones.
142
     *
143
     * @param array<mixed> $values
144
     * @param array<mixed>|string $uniqueBy
145
     * @param array<mixed>|null $update
146
     * @return int
147
     * @throws BindException
148
     */
149
    public function upsert(array $values, $uniqueBy, $update = null)
150
    {
151
        assert($this->grammar instanceof Grammar);
152
153
        if (empty($values)) {
154
            return 0;
155
        } elseif ($update === []) {
156
            return (int) $this->insert($values);
157
        }
158
159
        if (!is_array(reset($values))) {
160
            $values = [$values];
161
        }
162
163
        foreach ($values as $key => $value) {
164
            $values[$key] = $this->grammar->convertJsonFields($value);
165
            $values[$key] = $this->convertIdToKey($values[$key]);
0 ignored issues
show
Bug introduced by
It seems like convertIdToKey() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

165
            /** @scrutinizer ignore-call */ 
166
            $values[$key] = $this->convertIdToKey($values[$key]);
Loading history...
166
            $values[$key] = Arr::undot($values[$key]);
167
        }
168
169
        foreach ($values as $key => $value) {
170
            foreach ($value as $dataKey => $data) {
171
                $values[$key][$dataKey] = $this->bindValue($data, 'upsert');
172
            }
173
        }
174
175
        $uniqueBy = $this->grammar->convertJsonFields($uniqueBy);
176
177
        if (is_null($update)) {
178
            $update = array_keys(reset($values));
179
        }
180
181
        foreach ($update as $key => $value) {
182
            $update[$key] = $this->convertIdToKey($value);
183
        }
184
185
        $update = $this->grammar->convertJsonFields($update);
186
187
        $this->applyBeforeQueryCallbacks();
188
189
        $bindings = $this->bindings['upsert'];
190
191
        $aql = $this->grammar->compileUpsert($this, $values, (array) $uniqueBy, $update);
0 ignored issues
show
Bug introduced by
$this of type LaravelFreelancerNL\Aran...\Concerns\BuildsUpdates is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $query of LaravelFreelancerNL\Aran...rammar::compileUpsert(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

191
        $aql = $this->grammar->compileUpsert(/** @scrutinizer ignore-type */ $this, $values, (array) $uniqueBy, $update);
Loading history...
192
193
        return $this->connection->affectingStatement(
194
            $aql,
195
            $bindings,
196
        );
197
    }
198
}
199