MySQLProvider::loadRow()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 15
rs 9.9
1
<?php
2
/**
3
 * Livia
4
 * Copyright 2017-2019 Charlotte Dunois, All Rights Reserved
5
 *
6
 * Website: https://charuru.moe
7
 * License: https://github.com/CharlotteDunois/Livia/blob/master/LICENSE
8
*/
9
10
namespace CharlotteDunois\Livia\Providers;
11
12
/**
13
 * Loads and stores settings associated with guilds in a MySQL database. Requires the composer package react/mysql.
14
 */
15
class MySQLProvider extends SettingProvider {
16
    /**
17
     * The DB connection.
18
     * @var \React\MySQL\ConnectionInterface
19
     */
20
    protected $db;
21
    
22
    /**
23
     * A collection of a guild's settings, mapped by guild ID.
24
     * @var \CharlotteDunois\Collect\Collection
25
     */
26
    protected $settings;
27
    
28
    /**
29
     * Constructs a new instance.
30
     * @param \React\MySQL\ConnectionInterface  $db
31
     */
32
    function __construct(\React\MySQL\ConnectionInterface $db) {
33
        $this->db = $db;
34
        $this->providerState = \CharlotteDunois\Livia\Providers\SettingProvider::STATE_READY;
35
        
36
        $this->settings = new \CharlotteDunois\Collect\Collection();
37
    }
38
    
39
    /**
40
     * Resets the state.
41
     * @return void
42
     * @internal
43
     */
44
    function __wakeup() {
45
        $this->providerState = \CharlotteDunois\Livia\Providers\SettingProvider::STATE_IDLE;
46
    }
47
    
48
    /**
49
     * Returns the MySQL connection.
50
     * @return \React\MySQL\ConnectionInterface
51
     */
52
    function getDB() {
53
        return $this->db;
54
    }
55
    
56
    /**
57
     * {@inheritdoc}
58
     * @return \React\Promise\ExtendedPromiseInterface
59
     */
60
    function destroy() {
61
        $this->removeListeners();
62
        
63
        return $this->db->quit();
64
    }
65
    
66
    /**
67
     * {@inheritdoc}
68
     * @return \React\Promise\ExtendedPromiseInterface
69
     */
70
    function init(\CharlotteDunois\Livia\Client $client): \React\Promise\ExtendedPromiseInterface {
71
        $this->client = $client;
72
        $this->attachListeners();
73
        
74
        return (new \React\Promise\Promise(function (callable $resolve, callable $reject) {
75
            $this->runQuery('CREATE TABLE IF NOT EXISTS `settings` (`guild` VARCHAR(20) NOT NULL, `settings` TEXT NOT NULL, PRIMARY KEY (`guild`))')->then(function () {
76
                return $this->runQuery('SELECT * FROM `settings`')->then(function ($result) {
77
                    foreach($result->resultRows as $row) {
78
                        $this->loadRow($row);
79
                    }
80
                    
81
                    if($this->settings->has('global')) {
82
                        return null;
83
                    }
84
                    
85
                    return $this->create('global')->then(function () {
86
                        return null;
87
                    });
88
                });
89
            })->done($resolve, $reject);
90
        }));
91
    }
92
    
93
    /**
94
     * Creates a new table row in the db for the guild, if it doesn't exist already - otherwise loads the row.
95
     * @param string|\CharlotteDunois\Yasmin\Models\Guild  $guild
96
     * @param array|\ArrayObject                           $settings
97
     * @return \React\Promise\ExtendedPromiseInterface
98
     * @throws \InvalidArgumentException
99
     */
100
    function create($guild, $settings = array()): \React\Promise\ExtendedPromiseInterface {
101
        $guild = $this->getGuildID($guild);
102
        
103
        return $this->runQuery('SELECT * FROM `settings` WHERE `guild` = ?', array($guild))->then(function ($result) use ($guild, $settings) {
104
            if(empty($result->resultRows)) {
105
                if(!($settings instanceof \ArrayObject)) {
106
                    $settings = new \ArrayObject($settings, \ArrayObject::ARRAY_AS_PROPS);
107
                }
108
                
109
                $this->settings->set($guild, $settings);
110
                return $this->runQuery('INSERT INTO `settings` (`guild`, `settings`) VALUES (?, ?)', array($guild, \json_encode($settings)));
111
            } else {
112
                $this->loadRow($result->resultRows[0]);
113
            }
114
        });
115
    }
116
    
117
    /**
118
     * {@inheritdoc}
119
     * @return mixed
120
     */
121
    function get($guild, string $key, $defaultValue = null) {
122
        $guild = $this->getGuildID($guild);
123
        
124
        if($this->settings->get($guild) === null) {
125
            $this->client->emit('warn', 'Settings of specified guild is not loaded - loading row - returning default value');
126
            
127
            $this->create($guild);
128
            return $defaultValue;
129
        }
130
        
131
        $settings = $this->settings->get($guild);
132
        if(\array_key_exists($key, $settings)) {
133
            return $settings[$key];
134
        }
135
        
136
        return $defaultValue;
137
    }
138
    
139
    /**
140
     * {@inheritdoc}
141
     * @return \React\Promise\ExtendedPromiseInterface
142
     */
143
    function set($guild, string $key, $value) {
144
        $guild = $this->getGuildID($guild);
145
        
146
        if($this->settings->get($guild) === null) {
147
            return $this->create($guild)->then(function () use ($guild, $key, $value) {
148
                $settings = $this->settings->get($guild);
149
                $settings[$key] = $value;
150
            
151
                return $this->runQuery('UPDATE `settings` SET `settings` = ? WHERE `guild` = ?', array(\json_encode($settings), $guild));
152
            });
153
        }
154
        
155
        $settings = $this->settings->get($guild);
156
        $settings[$key] = $value;
157
        
158
        return $this->runQuery('UPDATE `settings` SET `settings` = ? WHERE `guild` = ?', array(\json_encode($settings), $guild));
159
    }
160
    
161
    /**
162
     * {@inheritdoc}
163
     * @return \React\Promise\ExtendedPromiseInterface
164
     */
165
    function remove($guild, string $key) {
166
        $guild = $this->getGuildID($guild);
167
        
168
        if($this->settings->get($guild) === null) {
169
            $this->client->emit('warn', 'Settings of specified guild is not loaded - loading row');
170
            
171
            return $this->create($guild)->then(function () use ($guild, $key) {
172
                $settings = $this->settings->get($guild);
173
                unset($settings[$key]);
174
            
175
                return $this->runQuery('UPDATE `settings` SET `settings` = ? WHERE `guild` = ?', array(\json_encode($settings), $guild));
176
            });
177
        }
178
        
179
        $settings = $this->settings->get($guild);
180
        unset($settings[$key]);
181
        
182
        return $this->runQuery('UPDATE `settings` SET `settings` = ? WHERE `guild` = ?', array(\json_encode($settings), $guild));
183
    }
184
    
185
    /**
186
     * {@inheritdoc}
187
     * @return \React\Promise\ExtendedPromiseInterface
188
     */
189
    function clear($guild) {
190
        $guild = $this->getGuildID($guild);
191
        
192
        return (new \React\Promise\Promise(function (callable $resolve, callable $reject) use ($guild) {
193
            $this->settings->delete($guild);
194
            $this->runQuery('DELETE FROM `settings` WHERE `guild` = ?', array($guild))->done($resolve, $reject);
195
        }));
196
    }
197
    
198
    /**
199
     * Runs a SQL query. Resolves with the QueryResult instance.
200
     * @param string  $sql
201
     * @param array   $parameters  Parameters for the query - these get escaped
202
     * @return \React\Promise\ExtendedPromiseInterface
203
     * @see https://github.com/friends-of-reactphp/mysql/blob/master/src/QueryResult.php
204
     */
205
    function runQuery(string $sql, array $parameters = array()) {
206
        return $this->db->query($sql, $parameters);
207
    }
208
    
209
    /**
210
     * Processes a database row.
211
     * @param array  $row
212
     * @return void
213
     */
214
    protected function loadRow(array $row) {
215
        $settings = \json_decode($row['settings'], true);
216
        if($settings === null) {
217
            $this->client->emit('warn', 'MySQLProvider couldn\'t parse the settings stored for guild "'.$row['guild'].'". Error: '.\json_last_error_msg());
218
            return;
219
        }
220
        
221
        $settings = new \ArrayObject($settings, \ArrayObject::ARRAY_AS_PROPS);
222
        $this->settings->set($row['guild'], $settings);
223
        
224
        try {
225
            $this->setupGuild($row['guild']);
226
        } catch (\InvalidArgumentException $e) {
227
            $this->settings->delete($row['guild']);
228
            $this->runQuery('DELETE FROM `settings` WHERE `guild` = ?', array($row['guild']))->done(null, array($this->client, 'handlePromiseRejection'));
229
        }
230
    }
231
}
232