Passed
Push — master ( 62d3ab...13ee47 )
by Charlotte
02:21
created

MySQLProvider::__sleep()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Livia
4
 * Copyright 2017-2018 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 __sleep() {
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\LiviaClient $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
                $this->settings->set($guild, $settings);
106
                return $this->runQuery('INSERT INTO `settings` (`guild`, `settings`) VALUES (?, ?)', array($guild, \json_encode($settings)));
107
            } else {
108
                $this->loadRow($result->resultRows[0]);
109
            }
110
        });
111
    }
112
    
113
    /**
114
     * {@inheritdoc}
115
     * @return mixed
116
     */
117
    function get($guild, string $key, $defaultValue = null) {
118
        $guild = $this->getGuildID($guild);
119
        
120
        if($this->settings->get($guild) === null) {
121
            $this->client->emit('warn', 'Settings of specified guild is not loaded - loading row - returning default value');
122
            
123
            $this->create($guild);
124
            return $defaultValue;
125
        }
126
        
127
        $settings = $this->settings->get($guild);
128
        if(\array_key_exists($key, $settings)) {
129
            return $settings[$key];
130
        }
131
        
132
        return $defaultValue;
133
    }
134
    
135
    /**
136
     * {@inheritdoc}
137
     * @return \React\Promise\ExtendedPromiseInterface
138
     */
139
    function set($guild, string $key, $value) {
140
        $guild = $this->getGuildID($guild);
141
        
142
        if($this->settings->get($guild) === null) {
143
            return $this->create($guild)->then(function () use ($guild, $key, $value) {
144
                $settings = $this->settings->get($guild);
145
                $settings[$key] = $value;
146
            
147
                return $this->runQuery('UPDATE `settings` SET `settings` = ? WHERE `guild` = ?', array(\json_encode($settings), $guild));
148
            });
149
        }
150
        
151
        $settings = $this->settings->get($guild);
152
        $settings[$key] = $value;
153
        
154
        return $this->runQuery('UPDATE `settings` SET `settings` = ? WHERE `guild` = ?', array(\json_encode($settings), $guild));
155
    }
156
    
157
    /**
158
     * {@inheritdoc}
159
     * @return \React\Promise\ExtendedPromiseInterface
160
     */
161
    function remove($guild, string $key) {
162
        $guild = $this->getGuildID($guild);
163
        
164
        if($this->settings->get($guild) === null) {
165
            $this->client->emit('warn', 'Settings of specified guild is not loaded - loading row');
166
            
167
            return $this->create($guild)->then(function () use ($guild, $key) {
168
                $settings = $this->settings->get($guild);
169
                unset($settings[$key]);
170
            
171
                return $this->runQuery('UPDATE `settings` SET `settings` = ? WHERE `guild` = ?', array(\json_encode($settings), $guild));
172
            });
173
        }
174
        
175
        $settings = $this->settings->get($guild);
176
        unset($settings[$key]);
177
        
178
        return $this->runQuery('UPDATE `settings` SET `settings` = ? WHERE `guild` = ?', array(\json_encode($settings), $guild));
179
    }
180
    
181
    /**
182
     * {@inheritdoc}
183
     * @return \React\Promise\ExtendedPromiseInterface
184
     */
185
    function clear($guild) {
186
        $guild = $this->getGuildID($guild);
187
        
188
        return (new \React\Promise\Promise(function (callable $resolve, callable $reject) use ($guild) {
189
            $this->settings->delete($guild);
190
            $this->runQuery('DELETE FROM `settings` WHERE `guild` = ?', array($guild))->done($resolve, $reject);
191
        }));
192
    }
193
    
194
    /**
195
     * Runs a SQL query. Resolves with the QueryResult instance.
196
     * @param string  $sql
197
     * @param array   $parameters  Parameters for the query - these get escaped
198
     * @return \React\Promise\ExtendedPromiseInterface
199
     * @see https://github.com/friends-of-reactphp/mysql/blob/master/src/QueryResult.php
200
     */
201
    function runQuery(string $sql, array $parameters = array()) {
202
        return $this->db->query($sql, $parameters);
203
    }
204
    
205
    /**
206
     * Processes a database row.
207
     * @param array  $row
208
     * @return void
209
     */
210
    protected function loadRow(array $row) {
211
        $settings = \json_decode($row['settings'], true);
212
        if($settings === null) {
213
            $this->client->emit('warn', 'MySQLProvider couldn\'t parse the settings stored for guild "'.$row['guild'].'". Error: '.\json_last_error_msg());
214
            return;
215
        }
216
        
217
        $settings = new \ArrayObject($settings, \ArrayObject::ARRAY_AS_PROPS);
218
        $this->settings->set($row['guild'], $settings);
219
        
220
        try {
221
            $this->setupGuild($row['guild']);
222
        } catch (\InvalidArgumentException $e) {
223
            $this->settings->delete($row['guild']);
224
            $this->runQuery('DELETE FROM `settings` WHERE `guild` = ?', array($row['guild']))->done(null, array($this->client, 'handlePromiseRejection'));
225
        }
226
    }
227
}
228