Completed
Push — master ( 757aaf...ac375b )
by Vitaly
03:09
created

Database::fetchColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 3
Metric Value
c 5
b 1
f 3
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 3
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: egorov
5
 * Date: 09.05.2015
6
 * Time: 13:05
7
 */
8
namespace samsonframework\orm;
9
use samsonframework\orm\exception\EntityNotFound;
10
11
/**
12
 * Class Database
13
 * @package samsonframework\orm
14
 */
15
class Database
16
{
17
    /** @var \PDO Database driver */
18
    protected $driver;
19
20
    /** @var string Database name */
21
    protected $database;
22
23
    /** @var int Amount of milliseconds spent on queries */
24
    protected $elapsed;
25
26
    /** @var int Amount queries executed */
27
    protected $count;
28
29
    /**
30
     * Connect to a database using driver with parameters
31
     * @param string $database Database name
32
     * @param string $username Database username
33
     * @param string $password Database password
34
     * @param string $host Database host(localhost by default)
35
     * @param int $port Database port(3306 by default)
36
     * @param string $driver Database driver for interaction(MySQL by default)
37
     * @param string $charset Database character set
38
     * @return bool True if connection to database was successful
39
     */
40
    public function connect(
41
        $database,
42
        $username,
43
        $password,
44
        $host = 'localhost',
45
        $port = 3306,
46
        $driver = 'mysql',
47
        $charset = 'utf8'
48
    ) {
49
        // If we have not connected yet
50
        if (!isset($this->driver)) {
51
            $this->database = $database;
52
53
            // Check if configured database exists
54
            $this->driver = new PDO($host, $database, $username, $password, $charset, $port, $driver);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \samsonframework\orm...harset, $port, $driver) of type object<samsonframework\orm\PDO> is incompatible with the declared type object<PDO> of property $driver.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
55
56
            // Set correct encodings
57
            $this->query("set character_set_client='utf8'");
58
            $this->query("set character_set_results='utf8'");
59
            $this->query("set collation_connection='utf8_general_ci'");
60
        }
61
    }
62
63
    /**
64
     * Get database name
65
     * @return string
66
     */
67
    public function database()
68
    {
69
        return $this->database;
70
    }
71
72
    /**
73
     * Create new database record
74
     * @param string $entity Entity class name
75
     * @return null|RecordInterface Entity instance
76
     * @throws EntityNotFound
77
     */
78 View Code Duplication
    public function entity($entity)
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...
79
    {
80
        if (class_exists($entity)) {
81
            return new $entity($this);
82
        } else {
83
            throw new EntityNotFound('['.$entity.'] not found');
84
        }
85
    }
86
87
    /**
88
     * Get entity query manager
89
     * @param string $entity Entity identifier
90
     * @return Query Query manager instance
91
     */
92
    public function manager($entity)
93
    {
94
        return new Query($entity, $this);
95
    }
96
97
    /**
98
     * Intreal error beautifier
99
     * @param \Exception $e
100
     * @param $sql
101
     */
102
    private function outputError(\Exception $e, $sql, $text = 'Error executing database query:')
103
    {
104
        elapsed('erorr');
105
        echo("\n" . '<div style="font-size:12px; position:relative; background:red; z-index:9999999;">'
106
        .'<div style="padding:4px 10px;">'.$text.'</div>'
107
            .'<div style="padding:0px 10px;">['.htmlspecialchars($e->getMessage()).']</div>'
108
            .'<textarea style="display:block; width:100%; min-height:100px;">'.$sql . '</textarea></div>');
109
    }
110
111
    /**
112
     * Proxy function for executing database fetching logic with exception,
113
     * error, profile handling
114
     * @param callback $fetcher Callback for fetching
115
     * @return mixed Fetching function result
116
     */
117
    private function execute($fetcher)
118
    {
119
        $result = array();
120
121
        if (isset($this->driver)) {
122
            // Store timestamp
123
            $tsLast = microtime(true);
124
125
            try { // Call fetcher
126
                // Get argument and remove first one
127
                $args = func_get_args();
128
                array_shift($args);
129
130
                // Proxy calling of fetcher function with passing parameters
131
                $result = call_user_func_array($fetcher, $args);
132
            } catch (\PDOException $e) {
133
                $this->outputError($e, $sql, 'Error executing ['.$fetcher.']');
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
134
            }
135
136
            // Store queries count
137
            $this->count++;
138
139
            // Count elapsed time
140
            $this->elapsed += microtime(true) - $tsLast;
141
        }
142
143
        return $result;
144
    }
145
146
    /**
147
     * High-level database query executor
148
     * @param string $sql SQL statement
149
     * @return mixed Database query result
150
     */
151
    private function innerQuery($sql)
152
    {
153
        try {
154
            // Perform database query
155
            $result = $this->driver->prepare($sql)->execute();
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
156
        } catch (\PDOException $e) {
157
            $this->outputError($e, $sql);
158
        }
159
    }
160
161
    /**
162
     * Retrieve array of records from a database, if $className is passed method
163
     * will try to create an object of that type. If request has failed than
164
     * method will return empty array of stdClass all arrays regarding to $className is
165
     * passed or not.
166
     *
167
     * @param string $sql Query text
168
     * @return array Collection of arrays or objects
169
     */
170
    private function innerFetch($sql, $className = null)
171
    {
172
        try {
173
            // Perform database query
174
            if (!isset($className)) { // Return array
175
                return $this->driver->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
176
            } else { // Create object of passed class name
177
                return $this->driver->query($sql)->fetchAll(\PDO::FETCH_CLASS, $className, array(&$this));
178
            }
179
        } catch (\PDOException $e) {
180
            $this->outputError($e, $sql, 'Fetching database records:');
181
        }
182
    }
183
184
    /**
185
     * Special accelerated function to retrieve db record fields instead of objects
186
     *
187
     * @param string $className
188
     * @param mixed $query
189
     * @param string $field
190
     *
191
     * @return array
192
     */
193
    private function innerFetchColumn($className, $query, $field)
194
    {
195
        // Get SQL
196
        $sql = $this->prepareSQL($className, $query);
0 ignored issues
show
Bug introduced by
The method prepareSQL() does not seem to exist on object<samsonframework\orm\Database>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
197
198
        // TODO: Remove old attributes retrieval
199
        // Get table column index by its name
200
        $columnIndex = array_search($field, array_values($className::$_table_attributes));
201
202
        try {
203
            // Perform database query
204
            return $this->driver->query($sql)->fetchAll(\PDO::FETCH_COLUMN, $columnIndex);
205
        } catch (\PDOException $e) {
206
            $this->outputError($e, $sql, 'Error fetching records column values:');
207
        }
208
    }
209
210
    /**
211
     * High-level database query executor
212
     * @param string $sql SQL statement
213
     * @return mixed Database query result
214
     */
215
    public function query($sql)
216
    {
217
        return $this->execute(array($this, 'innerQuery'), $sql);
218
    }
219
220
    /**
221
     * Retrieve array of records from a database, if $className is passed method
222
     * will try to create an object of that type. If request has failed than
223
     * method will return empty array of stdClass all arrays regarding to $className is
224
     * passed or not.
225
     *
226
     * @param string $sql Query text
227
     * @return array Collection of arrays or objects
228
     */
229
    public function fetch($sql)
230
    {
231
        return $this->execute(array($this, 'innerFetch'), $sql);
232
    }
233
234
    /**
235
     * Special accelerated function to retrieve db record fields instead of objects
236
     *
237
     * @param string $className
238
     * @param mixed $query
239
     * @param string $field
240
     *
241
     * @return array
242
     */
243
    public function fetchColumn($className, $query, $field)
244
    {
245
        return $this->execute(array($this, 'innerFetchColumn'), $className, $query, $field);
246
    }
247
248
    public function create($className, &$object = null)
249
    {
250
        // ??
251
        $fields = $this->getQueryFields($className, $object);
252
253
        $this->query('INSERT INTO `' . $className::$_table_name . '` (`'
254
            . implode('`,`', array_keys($fields)) . '`)
255
            VALUES (' . implode(',', $fields) . ')'
256
        );
257
258
        // Return last inserted row identifier
259
        return $this->driver->lastInsertId();
260
    }
261
262
    public function update($className, &$object)
263
    {
264
        $this->query('UPDATE `' . $className::$_table_name . '` SET '
265
            . implode(',', $this->getQueryFields($className, $object, true))
266
            . ' WHERE ' . $className::$_table_name . '.' . $className::$_primary . '="'
267
            . $object->id . '"');
268
    }
269
270
    public function delete($className, &$object)
271
    {
272
        $this->query('DELETE FROM `' . $className::$_table_name . '` WHERE '
273
            . $className::$_primary . ' = "' . $object->id . '"'
274
        );
275
    }
276
277
    /** Count query result */
278
    public function count($className, $query)
279
    {
280
        // Get SQL
281
        $sql = 'SELECT Count(*) as __Count FROM (' . $this->prepareSQL($className, $query) . ') as __table';
0 ignored issues
show
Bug introduced by
The method prepareSQL() does not seem to exist on object<samsonframework\orm\Database>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
282
283
        // Выполним запрос к БД
284
        $result = $this->fetch($sql);
285
286
        return $result[0]['__Count'];
287
    }
288
289
    /**
290
     * Выполнить защиту значения поля для его безопасного использования в запросах
291
     *
292
     * @param string $value Значения поля для запроса
293
     * @return string $value Безопасное представление значения поля для запроса
294
     */
295
    protected function protectQueryValue($value)
296
    {
297
        // If magic quotes are on - remove slashes
298
        if (get_magic_quotes_gpc()) {
299
            $value = stripslashes($value);
300
        }
301
302
        // Normally escape string
303
        $value = $this->driver->quote($value);
304
305
        // Return value in quotes
306
        return $value;
307
    }
308
309
    /**
310
     * Prepare create & update SQL statements fields
311
     * @param string $className Entity name
312
     * @param Record $object Database object to get values(if needed)
313
     * @param bool $straight Way of forming SQL field statements
314
     * @return array Collection of key => value with SQL fields statements
315
     */
316
    protected function &getQueryFields($className, & $object = null, $straight = false)
317
    {
318
        // Результирующая коллекция
319
        $collection = array();
320
321
        // Установим флаг получения значений атрибутов из переданного объекта
322
        $use_values = isset($object);
323
324
        // Переберем "настоящее" имена атрибутов схемы данных для объекта
325
        foreach ($className::$_table_attributes as $attribute => $map_attribute) {
326
            // Отметки времени не заполняем
327
            if ($className::$_types[$attribute] == 'timestamp') {
328
                continue;
329
            }
330
331
            // Основной ключ не заполняем
332
            if ($className::$_primary == $attribute) {
333
                continue;
334
            }
335
336
            // Получим значение атрибута объекта защитив от инъекций, если объект передан
337
            $value = $use_values ? $this->driver->quote($object->$map_attribute) : '';
338
339
            // Добавим значение поля, в зависимости от вида вывывода метода
340
            $collection[$map_attribute] = ($straight ? $className::$_table_name . '.' . $map_attribute . '=' : '') . $value;
341
        }
342
343
        // Вернем полученную коллекцию
344
        return $collection;
345
    }
346
347
    /** @deprecated Use query() */
348
    public function &simple_query($sql)
0 ignored issues
show
Coding Style introduced by
Method name "Database::simple_query" is not in camel caps format
Loading history...
349
    {
350
        return $this->query($sql);
351
    }
352
}
353