Completed
Push — master ( 0c7238...cc3d38 )
by Jitendra
02:55
created

Extension   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 16
dl 0
loc 144
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A insertAsBulk() 0 16 2
A getInclusiveColumns() 0 12 2
A countBy() 0 9 3
A registerLogger() 0 8 1
A clauseBinds() 0 10 4
A upsert() 0 20 4
1
<?php
2
3
namespace PhalconExt\Db;
4
5
use PhalconExt\Di\ProvidesDi;
6
7
/**
8
 * A cross platform extension to phalcon db adapter.
9
 *
10
 * @author  Jitendra Adhikari <[email protected]>
11
 * @license MIT
12
 *
13
 * @link    https://github.com/adhocore/phalcon-ext
14
 */
15
trait Extension
16
{
17
    use ProvidesDi;
18
19
    // Implemented by \Phalcon\Db\Adapter.
20
    abstract public function updateAsDict($table, $data, $conditions = null, $dataTypes = null);
21
22
    abstract public function insertAsDict($table, $data, $dataTypes = null);
23
24
    /**
25
     * Update a row matching given criteria if exists or insert new one.
26
     *
27
     * @param string $table    The table to act upon.
28
     * @param array  $data     The actual data dict ([field => value]) to update/insert.
29
     * @param array  $criteria The criteria dict ([field => value]) to match updatable row.
30
     *
31
     * @throws \InvalidArgumentException When the criteria is insufficient.
32
     *
33
     * @return bool
34
     */
35
    public function upsert(string $table, array $data, array $criteria): bool
36
    {
37
        if (empty($data)) {
38
            return false;
39
        }
40
41
        // Doesnt exist, insert new!
42
        if (0 === $count = $this->countBy($table, $criteria)) {
43
            return $this->insertAsDict($table, $data + $criteria);
44
        }
45
46
        // Ambiguous, multiple rows exist!
47
        if ($count > 1) {
48
            throw new \InvalidArgumentException('The criteria is not enough to fetch a single row for update!');
49
        }
50
51
        list($conditions, $bind) = $this->clauseBinds($criteria);
52
53
        // Update the existing data by criteria!
54
        return $this->updateAsDict($table, $data, \compact('conditions', 'bind'));
55
    }
56
57
    /**
58
     * Count rows in db table using given criteria.
59
     *
60
     * @param string $table
61
     * @param array  $criteria Col=>Val pairs
62
     *
63
     * @return int
64
     */
65
    public function countBy(string $table, array $criteria): int
66
    {
67
        if (empty($criteria)) {
68
            return 0;
69
        }
70
71
        list($clause, $binds) = $this->clauseBinds($criteria);
72
73
        return $this->fetchColumn("SELECT COUNT(1) FROM {$table} WHERE $clause", $binds) ?: 0;
1 ignored issue
show
Bug introduced by
It seems like fetchColumn() 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

73
        return $this->/** @scrutinizer ignore-call */ fetchColumn("SELECT COUNT(1) FROM {$table} WHERE $clause", $binds) ?: 0;
Loading history...
74
    }
75
76
    /**
77
     * Prepare clause and Binds using data dict.
78
     *
79
     * @param array $dict  Col=>Val pairs
80
     * @param bool  $named Whether to use named placeholder.
81
     *
82
     * @return array ['clause', [binds]]
83
     */
84
    public function clauseBinds(array $dict, bool $named = false): array
85
    {
86
        $fields = [];
87
        foreach ($dict as $key => $value) {
88
            $fields[] = $named ? "$key = :$key" : "$key = ?";
89
        }
90
91
        return [
92
            \implode(' AND ', $fields),
93
            $named ? $dict : \array_values($dict),
94
        ];
95
    }
96
97
    /**
98
     * Insert bulk data to a table in single query.
99
     *
100
     * @param string $table
101
     * @param array  $data
102
     *
103
     * @return bool
104
     */
105
    public function insertAsBulk(string $table, array $data): bool
106
    {
107
        $binds   = [];
108
        $columns = $this->getInclusiveColumns($data);
109
        $default = \array_fill_keys($columns, null);
110
111
        foreach ($data as $row) {
112
            $row   = \array_merge($default, $row);
113
            $binds = \array_merge($binds, \array_values($row));
114
        }
115
116
        $sql  = "INSERT INTO {$table} (" . \implode(',', $columns) . ') VALUES ';
117
        $set  = '(' . \rtrim(\str_repeat('?,', \count($columns)), ',') . ')';
118
        $sql .= \rtrim(\str_repeat($set . ',', \count($data)), ',');
119
120
        return $this->execute($sql, $binds);
0 ignored issues
show
Bug introduced by
It seems like execute() 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

120
        return $this->/** @scrutinizer ignore-call */ execute($sql, $binds);
Loading history...
121
    }
122
123
    /**
124
     * Get inclusive columns from multiple unbalanced/unorderd data dicts.
125
     *
126
     * @param array $data
127
     *
128
     * @return array
129
     */
130
    public function getInclusiveColumns(array $data): array
131
    {
132
        $columns = [];
133
134
        foreach (\array_filter($data, 'is_array') as $row) {
135
            $columns = \array_merge($columns, \array_keys($row));
136
        }
137
138
        $columns = \array_unique($columns);
139
        \sort($columns);
140
141
        return $columns;
142
    }
143
144
    /**
145
     * Register sql logger.
146
     *
147
     * @param array $config
148
     *
149
     * @return self
150
     */
151
    public function registerLogger(array $config): self
152
    {
153
        $evm = $this->di('eventsManager');
154
155
        $evm->attach('db', new Logger($config));
156
        $this->setEventsManager($evm);
0 ignored issues
show
Bug introduced by
It seems like setEventsManager() 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

156
        $this->/** @scrutinizer ignore-call */ 
157
               setEventsManager($evm);
Loading history...
157
158
        return $this;
159
    }
160
}
161