Completed
Branch master (5105da)
by Stefano
02:21
created

SQLConnection::value()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 3
eloc 4
nc 3
nop 3
1
<?php
2
3
/**
4
 * SQL
5
 *
6
 * SQL database access via PDO.
7
 *
8
 * @package core
9
 * @author [email protected]
10
 * @copyright Caffeina srl - 2015 - http://caffeina.it
11
 */
12
13
14
class SQL {
15
  use Module;
16
  protected static $connections = [],
17
                   $current     = 'default';
18
19
  public static function register($name, $dsn, $username=null, $password=null, $options=[]){
20
    return self::$connections[$name] = new SQLConnection($dsn, $username, $password, $options);
21
  }
22
23
  public static function connect($dsn, $username=null, $password=null, $options=[]){
24
    return self::register('default', $dsn, $username, $password, $options);
25
  }
26
27
  public static function defaultTo($name){
28 View Code Duplication
    if (isset(self::$connections[$name])){
1 ignored issue
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...
29
      self::$current = $name;
30
      return true;
31
    } else return false;
32
  }
33
34
  public static function close($name=null){
35
    if ($name === null) {
36
      foreach (self::$connections as $conn) $conn->close();
37
      return true;
38 View Code Duplication
    } else if (isset(self::$connections[$name])){
1 ignored issue
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...
39
      self::$connections->close();
0 ignored issues
show
Bug introduced by
The method close cannot be called on self::$connections (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
40
      return true;
41
    } else return false;
42
  }
43
44
  public static function using($name){
45
    if (empty(self::$connections[$name])) throw new \Exception("[SQL] Unknown connection named '$name'.");
46
    return self::$connections[$name];
47
  }
48
49
  public static function __callStatic($method, $args){
50
    if (empty(self::$connections[self::$current])) throw new \Exception("[SQL] No default connection defined.");
51
    return call_user_func_array([self::$connections[self::$current],$method],$args);
52
  }
53
54
}
55
56
// Default connection to in-memory ephemeral database
57
SQL::connect('sqlite::memory:');
58
59
60
class SQLConnection {
61
62
  protected $connection        = [],
63
            $queries           = [],
64
            $last_exec_success = true;
65
66
  public function __construct($dsn, $username=null, $password=null, $options=[]){
67
    $this->connection = [
68
      'dsn'        => $dsn,
69
      'pdo'        => null,
70
      'username'   => $username,
71
      'password'   => $password,
72
      'options'    => array_merge([
73
        PDO::ATTR_ERRMODE              => PDO::ERRMODE_EXCEPTION,
74
        PDO::ATTR_DEFAULT_FETCH_MODE   => PDO::FETCH_ASSOC,
75
        PDO::ATTR_EMULATE_PREPARES     => true,
76
      ],$options),
77
    ];
78
    // "The auto-commit mode cannot be changed for this driver" SQLite workaround
79
    if (strpos($dsn,'sqlite:') === 0) {
80
      $this->connection['options'] = $options;
81
    }
82
  }
83
84
  public function close(){
85
    $this->connection['pdo'] = null;
86
  }
87
88
  public function onConnect($callbkack){
0 ignored issues
show
Unused Code introduced by
The parameter $callbkack is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
89
    $this->connection['pdo'] = null;
90
  }
91
92
  public function connection(){
93
    if(empty($this->connection['pdo'])) {
94
      try {
95
        $this->connection['pdo'] = new PDO(
96
            $this->connection['dsn'],
97
            $this->connection['username'],
98
            $this->connection['password'],
99
            $this->connection['options']
100
101
        );
102
        Event::trigger('core.sql.connect',$this);
103
      } catch(Exception $e) {
104
        $this->connection['pdo'] = null;
105
      }
106
    }
107
    return $this->connection['pdo'];
108
  }
109
110
  public function prepare($query){
111
    if(!$this->connection()) return false;
112
    return isset($this->queries[$query]) ? $this->queries[$query] : ($this->queries[$query] = $this->connection()->prepare($query));
113
  }
114
115
  public function exec($query, $params=[]){
116
    if(!$this->connection()) return false;
117
118
    if (false==is_array($params)) $params = (array)$params;
119
    $query = Filter::with('core.sql.query',$query);
120
    if($statement = $this->prepare($query)){
121
      Event::trigger('core.sql.query',$query,$params,(bool)$statement);
122
123
      foreach ($params as $key => $val) {
124
        $type = PDO::PARAM_STR;
125
        if (is_bool($val)) {
126
          $type = PDO::PARAM_BOOL;
127
        } elseif (is_null($val)) {
128
          $type = PDO::PARAM_NULL;
129
        } elseif (is_int($val)) {
130
          $type = PDO::PARAM_INT;
131
        }
132
        // bindValue need a 1-based numeric parameter
133
        $statement->bindValue(is_numeric($key)?$key+1:':'.$key, $val, $type);
134
      }
135
    } else {
136
      Event::trigger('core.sql.error',$query,$params);
137
      return false;
138
    }
139
140
    $this->last_exec_success = $statement && $statement->execute();
141
    return $statement;
142
  }
143
144
  public function value($query, $params=[], $column=0){
145
    if(!$this->connection()) return false;
146
147
    $res = $this->exec($query,$params);
148
    return $res ? $res->fetchColumn($column) : null;
149
  }
150
151 View Code Duplication
  public function each($query, $params=[], callable $looper = null){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
152
    if(!$this->connection()) return false;
153
154
    // ($query,$looper) shorthand
155
    if ($looper===null && is_callable($params)) {$looper = $params; $params = [];}
156
    if( $res = $this->exec($query,$params) ){
157
      if(is_callable($looper))
158
        while ($row = $res->fetchObject()) $looper($row);
159
      else
160
        return $res->fetchAll(PDO::FETCH_CLASS);
161
    }
162
  }
163
164 View Code Duplication
  public function single($query, $params=[], callable $handler = null){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
165
    if(!$this->connection()) return false;
166
167
    // ($query,$handler) shorthand
168
    if ($handler===null && is_callable($params)) {$handler = $params; $params = [];}
169
    if( $res = $this->exec($query,$params) ){
170
        if (is_callable($handler))
171
          $handler($res->fetchObject());
172
        else
173
          return $res->fetchObject();
174
    }
175
  }
176
177
 public function run($script){
178
    if(!$this->connection()) return false;
179
180
    $sql_path = Options::get('database.sql.path',APP_DIR.'/sql');
181
    $sql_sep  = Options::get('database.sql.separator',';');
182
    if (is_file($f = "$sql_path/$script.sql")){
183
        $result = true;
184
        foreach(explode($sql_sep,file_get_contents($f)) as $statement) {
185
            $result = $this->exec($statement);
186
        }
187
        return $result;
188
    } else return false;
189
  }
190
191
  public function all($query, $params=[], callable $looper = null){
192
   if(!$this->connection()) return false;
193
   return $this->each($query,$params,$looper);
194
  }
195
196
  public function delete($table, $pks=null, $pk='id', $inclusive=true){
197
    if(!$this->connection()) return false;
198
199
    if (null===$pks) {
200
      return $this->exec("DELETE FROM `$table`");
201
    } else {
202
      return $this->exec("DELETE FROM `$table` WHERE `$pk` ".($inclusive ? "" : "NOT " )."IN (" . implode( ',', array_fill_keys( (array)$pks, '?' ) ) . ")",(array)$pks);
203
    }
204
  }
205
206
  public function insert($table, $data=[]){
207
    if(!$this->connection()) return false;
208
209
    if (false==is_array($data)) $data = (array)$data;
210
    $k = array_keys($data);
211
    asort($k);
212
    $pk = $k;
213
    array_walk($pk,function(&$e){ $e = ':'.$e;});
214
    $q = "INSERT INTO `$table` (`".implode('`,`',$k)."`) VALUES (".implode(',',$pk).")";
215
    $this->exec($q,$data);
216
    return $this->last_exec_success ? $this->connection()->lastInsertId() : false;
217
  }
218
219
  public function updateWhere($table, $data=[], $where){
220
    if(!$this->connection()) return false;
221
222
    if (false==is_array($data)) $data = (array)$data;
223
    if (empty($data)) return false;
224
    $k = array_keys($data);
225
    asort($k);
226
    array_walk($k,function(&$e){ $e = "`$e`=:$e";});
227
    $q = "UPDATE `$table` SET ".implode(', ',$k)." WHERE $where";
228
    $this->exec($q,$data);
229
    return $this->last_exec_success;
230
  }
231
232
  public function update($table, $data=[], $pk='id', $extra_where=''){
233
    return $this->updateWhere($table, $data, "`$pk`=:$pk $extra_where");
234
  }
235
236
  public function insertOrUpdate($table, $data=[], $pk='id', $extra_where=''){
237
    if(!$this->connection()) return false;
238
239
    if (false==is_array($data)) $data = (array)$data;
240
    if (empty($data[$pk])) return $this->insert($table, $data);
241
    if( (string) $this->value("SELECT `$pk` FROM `$table` WHERE `$pk`=? LIMIT 1", [$data[$pk]]) === (string) $data[$pk] ){
242
        return $this->update($table, $data, $pk, $extra_where);
243
    } else {
244
        return $this->insert($table, $data);
245
    }
246
  }
247
}
248
249