Completed
Push — master ( d21617...fb9e45 )
by Vitaly
02:38
created

dbMySQL::count()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 2
Metric Value
c 4
b 0
f 2
dl 0
loc 10
rs 9.4286
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
namespace samson\activerecord;
3
4
/**
5
 * Класс описывающий работу с MySQL
6
 * @author Vitaly Iegorov <[email protected]>
7
 * @author Nikita Kotenko <[email protected]>
8
 *
9
 */
10
class dbMySQL extends dbMySQLConnector
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
11
{
12
    /**
13
     * Количество запросов класса
14
     * @var integer
15
     */
16
    private $query_count = 0;
17
18
    /** Show hide query debug information */
19
    public function debug($flag = true)
20
    {
21
        if ($flag) {
22
            $_SESSION['__AR_SHOW_QUERY__'] = true;
23
        } else {
24
            unset($_SESSION['__AR_SHOW_QUERY__']);
25
        }
26
    }
27
28
    /**
29
     * Check object $field field value as $table column
30
     * and if database table does not have it - create.
31
     * $field is not set in object - error returns
32
     *
33
     * @param object $object Pointer to object to get field names data
34
     * @param string $table Database table name
35
     * @param string $field Object field name
36
     * @param string $type Database column name
37
     *
38
     * @return bool True if database table has field or field has been created
39
     */
40
    public function createField($object, $table, $field, $type = 'INT')
41
    {
42
        // Check if db identifier field is configured
43
        if (class_exists($table, false)) {
44
            if (strlen($object->$field)) {
45
                // Variable to get all social table attributes
46
                $attributes = array();
47
                // Get table attributes - PHP 5.2 compatible
48
                eval('$attributes = ' . $table . '::$_attributes;');
49
50
                // Remove namespaces
51
                $table = classname($table);
0 ignored issues
show
Deprecated Code introduced by
The function classname() has been deprecated with message: use \samson\core\AutoLoader::getOnlyClass()

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
52
53
                // Make keys lowercase
54
                $attributes = array_change_key_case_unicode($attributes);
55
56
                // If table does not have defined identifier field
57
                if (!isset($attributes[strtolower($object->$field)])) {
58
                    // Add identifier field to social users table
59
                    $this->simple_query('ALTER TABLE  `' . $table . '` ADD  `' . $object->$field . '` ' . $type . ' ');
0 ignored issues
show
Deprecated Code introduced by
The method samson\activerecord\dbMySQL::simple_query() has been deprecated with message: Use execute()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
60
                }
61
62
                return true;
63
64
            } else { // Signal error
65
                return e('Cannot load "' . get_class($object) . '" module - no $' . $field . ' is configured');
66
            }
67
        }
68
    }
69
70
    // TODO: Очень узкое место для совместимости с 5.2 !!!
71
    /**
72
     * Обратная совместить с PHP < 5.3 т.к. там нельзя подставлять переменное имя класса
73
     * в статическом контексте
74
     * @param unknown_type $class_name
75
     */
76
    public function __get_table_data($class_name)
0 ignored issues
show
Coding Style introduced by
Method name "dbMySQL::__get_table_data" is not in camel caps format
Loading history...
77
    {
78
        // Remove table prefix
79
        $class_name = str_replace(self::$prefix, '', $class_name);
80
81
        // Сформируем правильное имя класса
82
        $class_name = ns_classname($class_name, 'samson\activerecord');
0 ignored issues
show
Deprecated Code introduced by
The function ns_classname() has been deprecated with message: use \samson\core\AutoLoader::className() and pass full class name to it without splitting into class name and namespace

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
83
84
        // Сформируем комманды на получение статических переменных определенного класса
85
        $_table_name = '$_table_name = ' . $class_name . '::$_table_name;';
86
        $_own_group = '$_own_group = ' . $class_name . '::$_own_group;';
87
        $_table_attributes = '$_table_attributes = ' . $class_name . '::$_table_attributes;';
88
        $_primary = '$_primary = ' . $class_name . '::$_primary;';
89
        $_sql_from = '$_sql_from = ' . $class_name . '::$_sql_from;';
90
        $_sql_select = '$_sql_select = ' . $class_name . '::$_sql_select;';
91
        $_attributes = '$_attributes = ' . $class_name . '::$_attributes;';
92
        $_types = '$_types = ' . $class_name . '::$_types;';
93
        $_map = '$_map = ' . $class_name . '::$_map;';
94
        $_relations = '$_relations = ' . $class_name . '::$_relations;';
95
        $_unique = '$_unique = ' . $class_name . '::$_unique;';
96
        $_relation_type = '$_relation_type = ' . $class_name . '::$_relation_type;';
97
        $_relation_alias = '$_relation_alias = ' . $class_name . '::$_relation_alias;';
98
99
        //trace($_table_name.$_primary.$_sql_from.$_sql_select.$_map.$_attributes.$_relations.$_relation_type.$_types.$_unique);
100
101
        // Выполним специальный код получения значений переменной
102
        eval($_own_group . $_table_name . $_primary . $_sql_from . $_sql_select . $_map . $_attributes . $_relations . $_relation_type . $_relation_alias . $_types . $_unique . $_table_attributes);
103
104
        // Вернем массив имен переменных и их значений
105
        return array
106
        (
107
            '_table_name' => $_table_name,
108
            '_own_group' => $_own_group,
109
            '_primary' => $_primary,
110
            '_attributes' => $_attributes,
111
            '_table_attributes' => $_table_attributes,
112
            '_types' => $_types,
113
            '_map' => $_map,
114
            '_relations' => $_relations,
115
            '_relation_type' => $_relation_type,
116
            '_relation_alias' => $_relation_alias,
117
            '_sql_from' => $_sql_from,
118
            '_sql_select' => $_sql_select,
119
            '_unique' => $_unique,
120
        );
121
    }
122
123
    public function create($className, &$object = null)
124
    {
125
        // ??
126
        $fields = $this->getQueryFields($className, $object);
127
        // Build SQL query
128
        $sql = 'INSERT INTO `' . $className::$_table_name . '` (`'
129
            . implode('`,`', array_keys($fields)) . '`)
130
            VALUES (' . implode(',', $fields) . ')';
131
        $this->query($sql);
132
        // Return last inserted row identifier
133
        return $this->driver->lastInsertId();
134
    }
135
136
    public function update($className, &$object)
137
    {
138
        // ??
139
        $fields = $this->getQueryFields($className, $object, true);
140
        // Build SQL query
141
        $sql = 'UPDATE `' . $className::$_table_name . '` SET ' . implode(',',
142
                $fields) . ' WHERE ' . $className::$_table_name . '.' . $className::$_primary . '="' . $object->id . '"';
143
        $this->query($sql);
144
    }
145
146
    public function delete($className, &$object)
147
    {
148
        // Build SQL query
149
        $sql = 'DELETE FROM `' . $className::$_table_name . '` WHERE ' . $className::$_primary . ' = "' . $object->id . '"';
150
        $this->query($sql);
151
    }
152
153
    /**
154
     * @see idb::find()
155
     */
156
    public function &find($class_name, dbQuery $query)
157
    {
158
        // Результат выполнения запроса
159
        $result = array();
160
161
        if ($query->empty) {
162
            return $result;
163
        }
164
165
        // Get SQL
166
        $sql = $this->prepareSQL($class_name, $query);
167
168
        // Выполним запрос к БД
169
        $db_data = $this->fetch($sql);
0 ignored issues
show
Bug introduced by
It seems like $sql defined by $this->prepareSQL($class_name, $query) on line 166 can also be of type boolean; however, samsonframework\orm\Database::fetch() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
170
171
        //trace($query->virtual_fields);
172
173
        // Выполним запрос к БД и создадим объекты
174
        if ((is_array($db_data)) && (sizeof($db_data) > 0)) {
175
            $result = $this->toRecords($class_name, $db_data, $query->join,
176
                array_merge($query->own_virtual_fields, $query->virtual_fields));
177
        }
178
179
        // Вернем коллекцию полученных объектов
180
        return $result;
181
    }
182
183
184
    /**
185
     * @see idb::find_by_id()
186
     */
187
    public function &find_by_id($class_name, $id)
0 ignored issues
show
Coding Style introduced by
Method name "dbMySQL::find_by_id" is not in camel caps format
Loading history...
188
    {
189
        // Получим переменные для запроса
190
        extract($this->__get_table_data($class_name));
0 ignored issues
show
Bug introduced by
$this->__get_table_data($class_name) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
191
192
        // Выполним запрос к БД
193
        $record_data = $this->fetch('SELECT ' . $_sql_select['this'] . ' FROM ' . $_sql_from['this'] . ' WHERE ' . $_table_name . '.' . $_primary . ' = "' . $id . '"');
194
195
        // Если запрос выполнился успешно и получена минимум 1-на запись из БД - создадим объект-запись из неё
196
        $db_records = $this->toRecords($class_name, $record_data);
197
198
        // Переменная для возврата
199
        $ret = null;
200
201
        // Если мы получили 1ю запись то вернем её
202
        if (sizeof($db_records) >= 1) {
203
            $ret = array_shift($db_records);
204
        }
205
206
        // Вернем переменную
207
        return $ret;
208
    }
209
210
    /**
211
     * Выполнить защиту значения поля для его безопасного использования в запросах
212
     *
213
     * @param string $value Значения поля для запроса
214
     * @return string $value Безопасное представление значения поля для запроса
215
     */
216
    protected function protectQueryValue($value)
217
    {
218
        // If magic quotes are on - remove slashes
219
        if (get_magic_quotes_gpc()) {
220
            $value = stripslashes($value);
221
        }
222
223
        // Normally escape string
224
        $value = $this->driver->quote($value);
225
226
        // Return value in quotes
227
        return $value;
228
    }
229
230
    /** @deprecated Use execute() */
231
    public function &simple_query($sql)
0 ignored issues
show
Coding Style introduced by
Method name "dbMySQL::simple_query" is not in camel caps format
Loading history...
232
    {
233
        return $this->query($sql);
234
    }
235
236
    /** Count query result */
237
    public function count($className, $query)
238
    {
239
        // Get SQL
240
        $sql = 'SELECT Count(*) as __Count FROM (' . $this->prepareSQL($className, $query) . ') as __table';
241
242
        // Выполним запрос к БД
243
        $result = $this->fetch($sql);
244
245
        return $result[0]['__Count'];
246
    }
247
248
    /**
249
     * Prepare create & update SQL statements fields
250
     * @param string $className Entity name
251
     * @param Record $object Database object to get values(if needed)
252
     * @param bool $straight Way of forming SQL field statements
253
     * @return array Collection of key => value with SQL fields statements
254
     */
255
    protected function &getQueryFields($className, & $object = null, $straight = false)
256
    {
257
        // Результирующая коллекция
258
        $collection = array();
259
260
        // Установим флаг получения значений атрибутов из переданного объекта
261
        $use_values = isset($object);
262
263
        // Переберем "настоящее" имена атрибутов схемы данных для объекта
264
        foreach ($className::$_table_attributes as $attribute => $map_attribute) {
265
            // Отметки времени не заполняем
266
            if ($className::$_types[$attribute] == 'timestamp') {
267
                continue;
268
            }
269
270
            // Основной ключ не заполняем
271
            if ($className::$_primary == $attribute) {
272
                continue;
273
            }
274
275
            // Получим значение атрибута объекта защитив от инъекций, если объект передан
276
            $value = $use_values ? $this->driver->quote($object->$map_attribute) : '';
277
278
            // Добавим значение поля, в зависимости от вида вывывода метода
279
            $collection[$map_attribute] = ($straight ? $className::$_table_name . '.' . $map_attribute . '=' : '') . $value;
280
        }
281
282
        // Вернем полученную коллекцию
283
        return $collection;
284
    }
285
286
    /**
287
     * Generic database migration handler
288
     * @param string $classname Class for searching migration methods
289
     * @param string $version_handler External handler for interacting with database version
290
     */
291
    public function migration($classname, $version_handler)
292
    {
293
        if (!is_callable($version_handler)) {
294
            return e('No version handler is passed', E_SAMSON_ACTIVERECORD_ERROR);
295
        }
296
297
        // Get current database version
298
        $version = call_user_func($version_handler);
299
300
        // DB version migrating mechanism
301
        foreach (get_class_methods($classname) as $m) {
302
            // Parse migration method name to get migrating versions
303
            if (preg_match('/^migrate_(?<from>\d+)_to_(?<to>\d+)/i', $m, $matches)) {
304
                $from = $matches['from'];
305
                $to = $matches['to'];
306
307
                // If we found migration method from current db version
308
                if ($from == $version) {
309
                    // Run migration method
310
                    if (call_user_func(array($version_handler[0], $m)) !== false) {
311
                        // Save current version for further migrating
312
                        $version = $to;
313
314
                        // Call database version changing handler
315
                        call_user_func($version_handler, $to);
316
317
                        // Reload page
318
                        elapsed('Database migration from version: ' . $from . ' -> ' . $to);
319
                    } // Break and error
320
                    else {
321
                        e('Database migration from ## -> ## - has Failed', E_SAMSON_ACTIVERECORD_ERROR,
322
                            array($from, $to));
323
                        break;
324
                    }
325
                }
326
            }
327
        }
328
    }
329
330
    /** @see idb::profiler() */
331
    public function profiler()
332
    {
333
        // Выведем список объектов из БД
334
        $list = array();
335
336
        // Общее кво созданных объектов
337
        $total_obj_count = 0;
338
339
        // Переберм коллекции созданных объектов
340
        foreach (dbRecord::$instances as $n => $v) {
341
            // Если для данного класса были созданы объекты
342
            if ($c = sizeof($v)) {
343
                // Увеличим общий счетчик созданных объектов
344
                $total_obj_count += $c;
345
346
                // Выведем имя класса и кво созданных объектов
347
                $list[] = '' . $n . '(' . $c . ')';
348
            }
349
        }
350
351
        // Сформируем строку профайлинга
352
        return 'DB: ' . round($this->elapsed,
353
            3) . 'с, ' . $this->query_count . ' запр., ' . $total_obj_count . ' об.(' . implode($list, ',') . ')';
354
    }
355
356
    /** Count query result */
357
    public function innerCount($className, $query)
358
    {
359
        $params = $this->__get_table_data($className);
360
361
        // Get SQL
362
        $sql = 'SELECT Count(*) as __Count FROM (' .
363
            $this->prepareInnerSQL($className, $query, $params) .
364
            ') as __table';
365
366
        $result = $this->fetch($sql);
367
368
        return $result[0]['__Count'];
369
    }
370
371
    //
372
    // Приватный контекст
373
    //
374
375
    /**
376
     * Create SQL request
377
     *
378
     * @param string $class_name Classname for request creating
379
     * @param dbQuery $query Query with parameters
380
     * @return string SQL string
381
     */
382
    protected function prepareSQL($class_name, dbQuery $query)
383
    {
384
        //elapsed( 'dbMySQL::find() Начало');
385
        $params = $this->__get_table_data($class_name);
0 ignored issues
show
Documentation introduced by
$class_name is of type string, but the function expects a object<samson\activerecord\unknown_type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
386
        // Получим переменные для запроса
387
        extract($params);
388
389
        // Текст выборки полей
390
        $select = '`' . $_table_name . '`.*';//$_sql_select['this'];
391
392
        // If virtual fields defined
393 View Code Duplication
        if (sizeof($query->virtual_fields)) {
0 ignored issues
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...
394
            $select .= ', ' . "\n" . implode("\n" . ', ', $query->virtual_fields);
395
        }
396
397
        $from = ' ( ' . $this->prepareInnerSQL($class_name, $query, $params);
398
399
        // Добавим алиас
400
        $from .= ' ) as `' . $_table_name . '`';
401
402
        //trace($query->join);
403
404
        // Iterate related tables
405
        foreach ($query->join as $relation_data) {
406
            $c_table = self::$prefix . $relation_data->table;
407
408
            // Если существует требуемая связь
409
            if (isset($_sql_from[$c_table])) {
410
                // Получим текст для выборки данных из связанных таблиц
411
                $select .= ',' . $_sql_select[$c_table];
412
413
                // Получим текст для привязывания таблицы к запросу
414
                $from .= "\n" . ' ' . $_sql_from[$c_table];
415
            } else {
416
                return e('Ошибка! В таблице связей для класса(##), не указана связь с классом(##)',
417
                    E_SAMSON_FATAL_ERROR, array($class_name, $c_table));
418
            }
419
        }
420
421
        // Сформируем строку запроса на поиск записи
422
        $sql = "\n" . 'SELECT ' . $select . "\n" . ' FROM ' . $from;
423
424
        // Получим все условия запроса
425
        $sql .= "\n" . ' WHERE (' . $this->getConditions($query->condition, $class_name) . ')';
426
427
        // Добавим нужные сортировщики
428
        if (sizeof($query->group)) {
429
            $sql .= "\n" . ' GROUP BY ' . $query->group[0];
430
        }
431
        // Если указана сортировка результатов
432
        if (sizeof($query->order)) {
433
            $sql .= "\n" . ' ORDER BY ';
434
            for ($i = 0; $i < sizeof($query->order); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
435
                $item = &$query->order[$i];
436
                if (sizeof($item)) {
437
                    $sql .= $item[0] . ' ' . $item[1];
438
                }
439
                if ($i < (sizeof($query->order) - 1)) {
440
                    $sql .= ', ';
441
                }
442
            }
443
        }
444
        // Если нужно ограничить к-во записей в выдаче по главной таблице
445 View Code Duplication
        if (sizeof($query->limit)) {
0 ignored issues
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...
446
            $sql .= "\n" . ' LIMIT ' . $query->limit[0] . (isset($query->limit[1]) ? ',' . $query->limit[1] : '');
447
        }
448
449
        if (isset($GLOBALS['show_sql'])) {
450
            elapsed($sql);
451
        }
452
453
        return $sql;
454
    }
455
456
    protected function prepareInnerSQL($class_name, dbQuery $query, $params)
457
    {
458
        //trace($class_name);
459
        //print_r($query->own_condition);
460
        // Получим текст цели запроса
461
        $from = 'SELECT ' . $params['_sql_select']['this'];
462
463
        // Если заданны виртуальные поля, добавим для них колонки
464 View Code Duplication
        if (sizeof($query->own_virtual_fields)) {
0 ignored issues
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...
465
            $from .= ', ' . "\n" . implode("\n" . ', ', $query->own_virtual_fields);
466
        }
467
468
        // From part
469
        $from .= "\n" . ' FROM ' . $params['_sql_from']['this'];
470
471
        // Если существуют условия для главной таблицы в запросе - получим их
472
        if ($query->own_condition->size()) {
473
            $from .= "\n" . ' WHERE (' . $this->getConditions($query->own_condition, $class_name) . ')';
474
        }
475
476
        // Добавим нужные групировщики
477
        $query->own_group = array_merge($params['_own_group'],
478
            is_array($query->own_group) ? $query->own_group : array());
479 View Code Duplication
        if (sizeof($query->own_group)) {
0 ignored issues
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...
480
            $from .= "\n" . 'GROUP BY ' . implode(',', $query->own_group);
481
        }
482
        // Если указана сортировка результатов
483 View Code Duplication
        if (sizeof($query->own_order)) {
0 ignored issues
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...
484
            $from .= "\n" . ' ORDER BY ' . $query->own_order[0] . ' ' . $query->own_order[1];
485
        }
486
        // Если нужно ограничить к-во записей в выдаче по главной таблице
487 View Code Duplication
        if (sizeof($query->own_limit)) {
0 ignored issues
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...
488
            $from .= "\n" . ' LIMIT ' . $query->own_limit[0] . (isset($query->own_limit[1]) ? ',' . $query->own_limit[1] : '');
489
        }
490
491
        return $from;
492
    }
493
494
    protected function getConditions(Condition $cond_group, $class_name)
495
    {
496
        // Соберем сюда все сформированные условия для удобной "упаковки" их в строку
497
        $sql_condition = array();
498
499
        // Переберем все аргументы условий в условной группе условия
500
        foreach ($cond_group as $argument) {
501
            // Если аргумент я вляется группой аргументов, разпарсим его дополнительно
502
            if (is_a($argument, ns_classname('Condition', 'samson\activerecord'))) {
0 ignored issues
show
Deprecated Code introduced by
The function ns_classname() has been deprecated with message: use \samson\core\AutoLoader::className() and pass full class name to it without splitting into class name and namespace

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
503
                $sql_condition[] = $this->getConditions($argument, $class_name);
0 ignored issues
show
Documentation introduced by
$argument is of type object<samsonframework\orm\Argument>|false, but the function expects a object<samson\activerecord\Condition>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
504
            } else {
505
                // Если условие успешно разпознано - добавим его в коллекцию условий
506
                $sql_condition[] = $this->parseCondition($class_name, $argument);
0 ignored issues
show
Documentation introduced by
$argument is of type object<samsonframework\orm\Argument>|false, but the function expects a object<samson\activerecord\Argument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
507
            }
508
        }
509
510
        // Соберем все условия условной группы в строку
511
        if (sizeof($sql_condition)) {
512
            return '(' . implode(') ' . $cond_group->relation . ' (', $sql_condition) . ')';
513
        } // Вернем то что получилось
514
        else {
515
            return '(1=1)';
516
        }
517
    }
518
519
    /**
520
     * "Правильно" разпознать переданный аргумент условия запроса к БД
521
     *
522
     * @param string $class_name Схема сущности БД для которой данные условия
523
     * @param Argument $arg Аругемнт условия для преобразования
524
     * @return string Возвращает разпознанную строку с условием для MySQL
525
     */
526
    protected function parseCondition($class_name, & $arg)
527
    {
528
        // Получим переменные для запроса
529
        extract($this->__get_table_data($class_name));
0 ignored issues
show
Bug introduced by
$this->__get_table_data($class_name) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
Documentation introduced by
$class_name is of type string, but the function expects a object<samson\activerecord\unknown_type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
530
531
        // Получим "правильное" имя аттрибута сущности и выделим постоянную часть условия
532
        $sql_cond_t = isset($_map[$arg->field]) ? $_map[$arg->field] : $arg->field;
533
534
        // Если аргумент условия - это НЕ массив - оптимизации по более частому условию
535
        if (!is_array($arg->value)) {
536
            // NULL condition
537
            if ($arg->relation === dbRelation::NOTNULL || $arg->relation === dbRelation::ISNULL) {
538
                return $sql_cond_t . $arg->relation;
539
            } // Own condition
540
            else {
541
                if ($arg->relation === dbRelation::OWN) {
542
                    return $arg->field;
543
                } // Regular condition
544
                else {
545
                    return $sql_cond_t . $arg->relation . $this->protectQueryValue($arg->value);
546
                }
547
            }
548
        } // Если аргумент условия - это массив и в нем есть значения
549
        else {
550
            if (sizeof($arg->value)) {
551
                // TODO: Add other numeric types support
552
                // TODO: Get types of joined tables fields
553
554
                // Generate list of values, integer type optimization
555
                $sql_values = isset($class_name::$_types[$arg->field]) && $class_name::$_types[$arg->field] == 'int'
556
                    ? ' IN (' . implode(',', $arg->value) . ')'
557
                    : ' IN ("' . implode('","', $arg->value) . '")';
558
559
                switch ($arg->relation) {
560
                    case dbRelation::EQUAL:
561
                        return $sql_cond_t . $sql_values;
562
                    case dbRelation::NOT_EQUAL:
563
                        return $sql_cond_t . ' NOT ' . $sql_values;
564
                }
565
            }
566
        }
567
    }
568
569
570
    /**
571
     * Create object instance by specified parameters
572
     * @param string $className Object class name
573
     * @param RelationData $metaData Object metadata for creation and filling
0 ignored issues
show
Bug introduced by
There is no parameter named $metaData. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
574
     * @param array $dbData Database record with object data
575
     *
576
     * @return idbRecord Database record object instance
577
     */
578
    public function &createObject(
579
        $className,
580
        $identifier,
581
        array & $attributes,
582
        array & $dbData,
583
        array & $virtualFields = array()
584
    )
0 ignored issues
show
Coding Style introduced by
There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration; found newline
Loading history...
585
    {
586
        // If this object instance is not cached
587
        if (!isset(dbRecord::$instances[$className][$identifier]) || isset($dbData['__Count']) || sizeof($virtualFields)) {
588
589
            // Create empry dbRecord ancestor and store it to cache
590
            dbRecord::$instances[$className][$identifier] = new $className();
591
592
            // Pointer to object
593
            $object = &dbRecord::$instances[$className][$identifier];
594
595
            // Set object identifier
596
            $object->id = $identifier;
597
598
            // Fix object connection with DB record
599
            $object->attached = true;
600
601
            // Fill object attributes
602
            foreach ($attributes as $lc_field => $field) {
603
                $object->$lc_field = $dbData[$field];
604
            }
605
606
            // Fill virtual fields
607
            foreach ($virtualFields as $alias => $virtual_field) {
608
                // If DB record contains virtual field data
609
                if (isset($dbData[$alias])) {
610
                    $object->$alias = $dbData[$alias];
611
                }
612
            }
613
614
            return $object;
615
616
        } else { // Get object instance from cache
617
            return dbRecord::$instances[$className][$identifier];
618
        }
619
    }
620
621
    /**
622
     * Преобразовать массив записей из БД во внутреннее представление dbRecord
623
     * @param string $class_name Имя класса
624
     * @param array $response Массив записей полученных из БД
625
     * @return array Коллекцию записей БД во внутреннем формате
626
     * @see dbRecord
627
     */
628
    protected function &toRecords($class_name, array & $response, array $join = array(), array $virtual_fields = array())
629
    {
630
        // Сформируем правильное имя класса
631
        $class_name = ns_classname($class_name, 'samson\activerecord');
0 ignored issues
show
Deprecated Code introduced by
The function ns_classname() has been deprecated with message: use \samson\core\AutoLoader::className() and pass full class name to it without splitting into class name and namespace

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
632
633
        // Результирующая коллекция полученных записей из БД
634
        $collection = array();
635
636
        // Получим переменные для запроса
637
        extract($this->__get_table_data($class_name));
0 ignored issues
show
Bug introduced by
$this->__get_table_data($class_name) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
Documentation introduced by
$class_name is of type string, but the function expects a object<samson\activerecord\unknown_type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
638
639
        // Generate table metadata for joined tables
640
        $joinedTableData = array();
641
        foreach ($join as $relationData) {
642
643
            // Generate full joined table name(including prefix)
644
            $joinTable = self::$prefix . $relationData->table;
645
646
            // Get real classname of the table without alias
647
            $tableName = $_relation_alias[$joinTable];
648
649
            // Get joined table class metadata
650
            $joinedTableData[$tableName] = $this->__get_table_data($tableName);
651
        }
652
653
        // Получим имя главного
654
        $main_primary = $_primary;
655
656
        // Перебем массив полученных данных от БД - создадим для них объекты
657
        $records_count = sizeof($response);
658
659
        // Идентификатор текущего создаваемого объекта
660
        $main_id = isset($response[0]) ? $response[0][$main_primary] : 0;
661
662
        // Указатель на текущий обрабатываемый объект
663
        $main_obj = null;
664
665
        // Переберем полученные записи из БД
666
        for ($i = 0; $i < $records_count; $i++) {
667
            // Строка данных полученная из БД
668
            $db_row = &$response[$i];
669
670
            // Get object instance
671
            $collection[$main_id] = &$this->createObject($class_name, $main_id, $_attributes, $db_row, $virtual_fields);
672
673
            // Pointer to main object
674
            $main_obj = &$collection[$main_id];
675
676
            // Выполним внутренний перебор строк из БД начиная с текущей строки
677
            // Это позволит нам розабрать объекты полученные со связью один ко многим
678
            // А если это связь 1-1 то цикл выполниться только один раз
679
            for ($j = $i; $j < $records_count; $j++) {
680
                // Строка данных полученная из БД
681
                $db_inner_row = &$response[$j];
682
683
                // Получим идентфиикатор главного объекта в текущей строче БД
684
                $obj_id = $db_inner_row[$main_primary];
685
686
                // Если в строке из БД новый идентификатор
687
                if ($obj_id != $main_id) {
688
                    // Установим новый текущий идентификатор материала
689
                    $main_id = $obj_id;
690
691
                    // Установим индекс главного цикла на строку с новым главным элементом
692
                    // учтем что главный цикл сам увеличит на единицу индекс
693
                    $i = $j - 1;
694
695
                    //trace(' - Найден новый объект на строке №'.$j.'-'.$db_inner_row[$main_primary]);
696
697
                    // Прервем внутренний цикл
698
                    break;
699
                }
700
                //else trace(' + Заполняем данные из строки №'.$j);
701
702
                // Переберем все присоединенные таблицы в запросе
703
                foreach ($join as $relation_data) {
704
                    /**@var \samson\activerecord\RelationData $relation_data */
705
706
                    // If this table is not ignored
707
                    if (!$relation_data->ignore) {
708
709
                        // TODO: Prepare all data in RelationObject to speed up this method
710
711
                        $join_name = $relation_data->relation;
712
713
                        $join_table = self::$prefix . $relation_data->table;
714
715
                        //trace('Filling related table:'.$join_name.'/'.$join_table);
716
717
                        // Get real classname of the table without alias
718
                        $_relation_name = $_relation_alias[$join_table];
719
                        $join_class = str_replace(self::$prefix, '', $relation_data->table);
720
721
                        // Get joined table metadata from previously prepared object
722
                        $r_data = $joinedTableData[$_relation_name];
723
724
                        // Try to get identifier
725
                        if (isset($_relations[$join_table][$r_data['_primary']])) {
726
                            $r_obj_id_field = $_relations[$join_table][$r_data['_primary']];
727
                        } // Получим имя ключевого поля связанного объекта
728
                        else {
729
                            e('Cannot find related table(##) primary field(##) description',
730
                                E_SAMSON_ACTIVERECORD_ERROR, array($join_table, $r_data['_primary']));
731
                        }
732
733
                        // Если задано имя ключевого поля связанного объекта - создадим его
734
                        if (isset($db_inner_row[$r_obj_id_field])) {
735
                            // Получим ключевое поле связанного объекта
736
                            $r_obj_id = $db_inner_row[$r_obj_id_field];
737
738
                            // Get joined object instance
739
                            $r_obj = &$this->createObject($join_name, $r_obj_id, $_relations[$join_table],
740
                                $db_inner_row);
741
742
                            // Call handler for object filling
743
                            $r_obj->filled();
744
745
                            // TODO: Это старый подход - сохранять не зависимо от алиаса под реальным именем таблицы
746
747
                            // Если связанный объект привязан как один-к-одному - просто довами ссылку на него
748
                            if ($_relation_type[$join_table] == 0) {
749
                                $main_obj->onetoone['_' . $join_table] = $r_obj;
750
                                $main_obj->onetoone['_' . $join_class] = $r_obj;
751
                            } // Иначе создадим массив типа: идентификатор -> объект
752
                            else {
753
                                $main_obj->onetomany['_' . $join_table][$r_obj_id] = $r_obj;
754
                                $main_obj->onetomany['_' . $join_class][$r_obj_id] = $r_obj;
755
                            }
756
                        }
757
                    }
758
                }
759
            }
760
761
            // Call handler for object filling
762
            $main_obj->filled();
763
764
            // Если внутренний цикл дошел до конца остановим главный цикл
765
            if ($j == $records_count) {
766
                break;
767
            }
768
        }
769
770
        // Вернем то что у нас вышло
771
        return $collection;
772
    }
773
}
774