ORM_Core   D
last analyzed

Complexity

Total Complexity 187

Size/Duplication

Total Lines 1252
Duplicated Lines 0.48 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 3
Bugs 2 Features 0
Metric Value
c 3
b 2
f 0
dl 6
loc 1252
rs 4.4102
wmc 187
lcom 1
cbo 1

39 Methods

Rating   Name   Duplication   Size   Complexity  
A factory() 0 7 1
B __construct() 0 25 4
B __initialize() 0 25 5
A __sleep() 0 5 1
A __wakeup() 0 10 2
C __call() 6 56 11
C __get() 0 74 13
D __set() 0 34 9
A __isset() 0 4 2
A __unset() 0 4 1
A __toString() 0 4 1
A as_array() 0 11 2
C with() 0 69 9
A find() 0 14 3
B find_all() 0 14 5
A select_list() 0 13 3
C validate() 0 45 11
D save() 0 93 17
A delete() 0 12 3
A delete_all() 0 18 3
A clear() 0 11 1
A reload() 0 4 1
A reload_columns() 0 14 4
C has() 0 29 7
A add() 0 19 3
B remove() 0 23 4
A count_all() 0 5 1
A list_fields() 0 9 2
A field_data() 0 5 1
A clear_cache() 0 9 1
A unique_key() 0 4 1
D foreign_key() 0 41 9
A join_table() 0 10 2
B related_object() 0 14 5
C load_values() 0 37 7
C load_type() 0 44 16
C load_result() 0 59 12
A load_relations() 0 21 2
A empty_primary_key() 0 4 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ORM_Core often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ORM_Core, and based on these observations, apply Extract Interface, too.

1
<?php defined('SYSPATH') or die('No direct access allowed.');
2
/**
3
 * [Object Relational Mapping][ref-orm] (ORM) is a method of abstracting database
4
 * access to standard PHP calls. All table rows are represented as model objects,
5
 * with object properties representing row data. ORM in Kohana generally follows
6
 * the [Active Record][ref-act] pattern.
7
 *
8
 * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping
9
 * [ref-act]: http://wikipedia.org/wiki/Active_record
10
 *
11
 * $Id: ORM.php 4354 2009-05-15 16:51:37Z kiall $
12
 *
13
 * @package    ORM
14
 * @author     Kohana Team
15
 * @copyright  (c) 2007-2008 Kohana Team
16
 * @license    http://kohanaphp.com/license.html
17
 */
18
class ORM_Core
19
{
20
21
    // Current relationships
22
    protected $has_one                 = array();
23
    protected $belongs_to              = array();
24
    protected $has_many                = array();
25
    protected $has_and_belongs_to_many = array();
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $has_and_belongs_to_many exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
26
27
    // Relationships that should always be joined
28
    protected $load_with = array();
29
30
    // Current object
31
    protected $object  = array();
32
    protected $changed = array();
33
    protected $related = array();
34
    protected $loaded  = false;
35
    protected $saved   = false;
36
    protected $sorting;
37
38
    // Related objects
39
    protected $object_relations = array();
40
    protected $changed_relations = array();
41
42
    // Model table information
43
    protected $object_name;
44
    protected $object_plural;
45
    protected $table_name;
46
    protected $table_columns;
47
    protected $ignored_columns;
48
49
    // Table primary key and value
50
    protected $primary_key = 'id';
51
    protected $primary_val = 'name';
52
53
    // Array of foreign key name overloads
54
    protected $foreign_key = array();
55
56
    // Model configuration
57
    protected $table_names_plural = true;
58
    protected $reload_on_wakeup   = true;
59
60
    // Database configuration
61
    protected $db = 'default';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $db. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
62
    protected $db_applied = array();
63
64
    // With calls already applied
65
    protected $with_applied = array();
66
67
    // Stores column information for ORM models
68
    protected static $column_cache = array();
69
70
    /**
71
     * Creates and returns a new model.
72
     *
73
     * @chainable
74
     * @param   string  model name
75
     * @param   mixed   parameter for find()
76
     * @return  ORM
77
     */
78
    public static function factory($model, $id = null)
79
    {
80
        // Set class name
81
        $model = ucfirst($model).'_Model';
82
83
        return new $model($id);
84
    }
85
86
    /**
87
     * Prepares the model database connection and loads the object.
88
     *
89
     * @param   mixed  parameter for find or object to load
90
     * @return  void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
91
     */
92
    public function __construct($id = null)
93
    {
94
        // Set the object name and plural name
95
        $this->object_name   = strtolower(substr(get_class($this), 0, -6));
96
        $this->object_plural = inflector::plural($this->object_name);
97
98
        if (!isset($this->sorting)) {
99
            // Default sorting
100
            $this->sorting = array($this->primary_key => 'asc');
101
        }
102
103
        // Initialize database
104
        $this->__initialize();
105
106
        // Clear the object
107
        $this->clear();
108
109
        if (is_object($id)) {
110
            // Load an object
111
            $this->load_values((array) $id);
112
        } elseif (!empty($id)) {
113
            // Find an object
114
            $this->find($id);
115
        }
116
    }
117
118
    /**
119
     * Prepares the model database connection, determines the table name,
120
     * and loads column information.
121
     *
122
     * @return  void
123
     */
124
    public function __initialize()
125
    {
126
        if (! is_object($this->db)) {
127
            // Get database instance
128
            $this->db = Database::instance($this->db);
129
        }
130
131
        if (empty($this->table_name)) {
132
            // Table name is the same as the object name
133
            $this->table_name = $this->object_name;
134
135
            if ($this->table_names_plural === true) {
136
                // Make the table name plural
137
                $this->table_name = inflector::plural($this->table_name);
138
            }
139
        }
140
141
        if (is_array($this->ignored_columns)) {
142
            // Make the ignored columns mirrored = mirrored
143
            $this->ignored_columns = array_combine($this->ignored_columns, $this->ignored_columns);
144
        }
145
146
        // Load column information
147
        $this->reload_columns();
148
    }
149
150
    /**
151
     * Allows serialization of only the object data and state, to prevent
152
     * "stale" objects being unserialized, which also requires less memory.
153
     *
154
     * @return  string[]
155
     */
156
    public function __sleep()
157
    {
158
        // Store only information about the object
159
        return array('object_name', 'object', 'changed', 'loaded', 'saved', 'sorting');
160
    }
161
162
    /**
163
     * Prepares the database connection and reloads the object.
164
     *
165
     * @return  void
166
     */
167
    public function __wakeup()
168
    {
169
        // Initialize database
170
        $this->__initialize();
171
172
        if ($this->reload_on_wakeup === true) {
173
            // Reload the object
174
            $this->reload();
175
        }
176
    }
177
178
    /**
179
     * Handles pass-through to database methods. Calls to query methods
180
     * (query, get, insert, update) are not allowed. Query builder methods
181
     * are chainable.
182
     *
183
     * @param   string  method name
184
     * @param   array   method arguments
185
     * @return  mixed
186
     */
187
    public function __call($method, array $args)
188
    {
189
        if (method_exists($this->db, $method)) {
190
            if (in_array($method, array('query', 'get', 'insert', 'update', 'delete'))) {
191
                throw new Kohana_Exception('orm.query_methods_not_allowed');
192
            }
193
194
            // Method has been applied to the database
195
            $this->db_applied[$method] = $method;
196
197
            // Number of arguments passed
198
            $num_args = count($args);
199
200
            if ($method === 'select' and $num_args > 3) {
201
                // Call select() manually to avoid call_user_func_array
202
                $this->db->select($args);
0 ignored issues
show
Bug introduced by
The method select cannot be called on $this->db (of type string).

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...
203
            } else {
204
                // We use switch here to manually call the database methods. This is
205
                // done for speed: call_user_func_array can take over 300% longer to
206
                // make calls. Most database methods are 4 arguments or less, so this
207
                // avoids almost any calls to call_user_func_array.
208
209
                switch ($num_args) {
210
                    case 0:
211
                        if (in_array($method, array('open_paren', 'close_paren', 'enable_cache', 'disable_cache'))) {
212
                            // Should return ORM, not Database
213
                            $this->db->$method();
0 ignored issues
show
Bug introduced by
The method $method cannot be called on $this->db (of type string).

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...
214
                        } else {
215
                            // Support for things like reset_select, reset_write, list_tables
216
                            return $this->db->$method();
0 ignored issues
show
Bug introduced by
The method $method cannot be called on $this->db (of type string).

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...
217
                        }
218
                    break;
219
                    case 1:
220
                        $this->db->$method($args[0]);
0 ignored issues
show
Bug introduced by
The method $method cannot be called on $this->db (of type string).

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...
221
                    break;
222
                    case 2:
223
                        $this->db->$method($args[0], $args[1]);
0 ignored issues
show
Bug introduced by
The method $method cannot be called on $this->db (of type string).

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...
224
                    break;
225 View Code Duplication
                    case 3:
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...
226
                        $this->db->$method($args[0], $args[1], $args[2]);
0 ignored issues
show
Bug introduced by
The method $method cannot be called on $this->db (of type string).

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...
227
                    break;
228 View Code Duplication
                    case 4:
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...
229
                        $this->db->$method($args[0], $args[1], $args[2], $args[3]);
0 ignored issues
show
Bug introduced by
The method $method cannot be called on $this->db (of type string).

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...
230
                    break;
231
                    default:
232
                        // Here comes the snail...
233
                        call_user_func_array(array($this->db, $method), $args);
234
                    break;
235
                }
236
            }
237
238
            return $this;
239
        } else {
240
            throw new Kohana_Exception('core.invalid_method', $method, get_class($this));
241
        }
242
    }
243
244
    /**
245
     * Handles retrieval of all model values, relationships, and metadata.
246
     *
247
     * @param   string  column name
248
     * @return  mixed
249
     */
250
    public function __get($column)
251
    {
252
        if (array_key_exists($column, $this->object)) {
253
            return $this->object[$column];
254
        } elseif (isset($this->related[$column])) {
255
            return $this->related[$column];
256
        } elseif ($column === 'primary_key_value') {
257
            return $this->object[$this->primary_key];
258
        } elseif ($model = $this->related_object($column)) {
259
            // This handles the has_one and belongs_to relationships
260
261
            if (in_array($model->object_name, $this->belongs_to) or ! array_key_exists($this->foreign_key($column), $model->object)) {
262
                // Foreign key lies in this table (this model belongs_to target model) OR an invalid has_one relationship
263
                $where = array($model->table_name.'.'.$model->primary_key => $this->object[$this->foreign_key($column)]);
264
            } else {
265
                // Foreign key lies in the target table (this model has_one target model)
266
                $where = array($this->foreign_key($column, $model->table_name) => $this->primary_key_value);
0 ignored issues
show
Bug introduced by
The property primary_key_value does not seem to exist. Did you mean primary_key?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
267
            }
268
269
            // one<>alias:one relationship
270
            return $this->related[$column] = $model->find($where);
271
        } elseif (isset($this->has_many[$column])) {
272
            // Load the "middle" model
273
            $through = ORM::factory(inflector::singular($this->has_many[$column]));
274
275
            // Load the "end" model
276
            $model = ORM::factory(inflector::singular($column));
277
278
            // Join ON target model's primary key set to 'through' model's foreign key
279
            // User-defined foreign keys must be defined in the 'through' model
280
            $join_table = $through->table_name;
281
            $join_col1  = $through->foreign_key($model->object_name, $join_table);
282
            $join_col2  = $model->table_name.'.'.$model->primary_key;
283
284
            // one<>alias:many relationship
285
            return $this->related[$column] = $model
286
                ->join($join_table, $join_col1, $join_col2)
287
                ->where($through->foreign_key($this->object_name, $join_table), $this->object[$this->primary_key])
288
                ->find_all();
289
        } elseif (in_array($column, $this->has_many)) {
290
            // one<>many relationship
291
            $model = ORM::factory(inflector::singular($column));
292
            return $this->related[$column] = $model
293
                ->where($this->foreign_key($column, $model->table_name), $this->object[$this->primary_key])
294
                ->find_all();
295
        } elseif (in_array($column, $this->has_and_belongs_to_many)) {
296
            // Load the remote model, always singular
297
            $model = ORM::factory(inflector::singular($column));
298
299
            if ($this->has($model, true)) {
300
                // many<>many relationship
301
                return $this->related[$column] = $model
302
                    ->in($model->table_name.'.'.$model->primary_key, $this->changed_relations[$column])
303
                    ->find_all();
304
            } else {
305
                // empty many<>many relationship
306
                return $this->related[$column] = $model
307
                    ->where($model->table_name.'.'.$model->primary_key, null)
308
                    ->find_all();
309
            }
310
        } elseif (isset($this->ignored_columns[$column])) {
311
            return null;
312
        } elseif (in_array($column, array(
313
                'object_name', 'object_plural', // Object
314
                'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
315
                'loaded', 'saved', // Status
316
                'has_one', 'belongs_to', 'has_many', 'has_and_belongs_to_many', 'load_with' // Relationships
317
            ))) {
318
            // Model meta information
319
            return $this->$column;
320
        } else {
321
            throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
322
        }
323
    }
324
325
    /**
326
     * Handles setting of all model values, and tracks changes between values.
327
     *
328
     * @param   string  column name
329
     * @param   mixed   column value
330
     * @return  void
331
     */
332
    public function __set($column, $value)
333
    {
334
        if (isset($this->ignored_columns[$column])) {
335
            return null;
336
        } elseif (isset($this->object[$column]) or array_key_exists($column, $this->object)) {
337
            if (isset($this->table_columns[$column])) {
338
                // Data has changed
339
                $this->changed[$column] = $column;
340
341
                // Object is no longer saved
342
                $this->saved = false;
343
            }
344
345
            $this->object[$column] = $this->load_type($column, $value);
346
        } elseif (in_array($column, $this->has_and_belongs_to_many) and is_array($value)) {
347
            // Load relations
348
            $model = ORM::factory(inflector::singular($column));
349
350
            if (! isset($this->object_relations[$column])) {
351
                // Load relations
352
                $this->has($model);
353
            }
354
355
            // Change the relationships
356
            $this->changed_relations[$column] = $value;
357
358
            if (isset($this->related[$column])) {
359
                // Force a reload of the relationships
360
                unset($this->related[$column]);
361
            }
362
        } else {
363
            throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
364
        }
365
    }
366
367
    /**
368
     * Checks if object data is set.
369
     *
370
     * @param   string  column name
371
     * @return  boolean
372
     */
373
    public function __isset($column)
374
    {
375
        return (isset($this->object[$column]) or isset($this->related[$column]));
376
    }
377
378
    /**
379
     * Unsets object data.
380
     *
381
     * @param   string  column name
382
     * @return  void
383
     */
384
    public function __unset($column)
385
    {
386
        unset($this->object[$column], $this->changed[$column], $this->related[$column]);
387
    }
388
389
    /**
390
     * Displays the primary key of a model when it is converted to a string.
391
     *
392
     * @return  string
393
     */
394
    public function __toString()
395
    {
396
        return (string) $this->object[$this->primary_key];
397
    }
398
399
    /**
400
     * Returns the values of this object as an array.
401
     *
402
     * @return  array
403
     */
404
    public function as_array()
405
    {
406
        $object = array();
407
408
        foreach ($this->object as $key => $val) {
409
            // Reconstruct the array (calls __get)
410
            $object[$key] = $this->$key;
411
        }
412
413
        return $object;
414
    }
415
416
    /**
417
     * Binds another one-to-one object to this model.  One-to-one objects
418
     * can be nested using 'object1:object2' syntax
419
     *
420
     * @param string $target_path
421
     * @return ORM_Core
422
     */
423
    public function with($target_path)
424
    {
425
        if (isset($this->with_applied[$target_path])) {
426
            // Don't join anything already joined
427
            return $this;
428
        }
429
430
        // Split object parts
431
        $objects = explode(':', $target_path);
432
        $target     = $this;
433
        foreach ($objects as $object) {
434
            // Go down the line of objects to find the given target
435
            $parent = $target;
436
            $target = $parent->related_object($object);
437
438
            if (! $target) {
439
                // Can't find related object
440
                return $this;
441
            }
442
        }
443
444
        $target_name = $object;
0 ignored issues
show
Bug introduced by
The variable $object seems to be defined by a foreach iteration on line 433. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
445
446
        // Pop-off top object to get the parent object (user:photo:tag becomes user:photo - the parent table prefix)
447
        array_pop($objects);
448
        $parent_path = implode(':', $objects);
449
450
        if (empty($parent_path)) {
451
            // Use this table name itself for the parent object
452
            $parent_path = $this->table_name;
453
        } else {
454
            if (! isset($this->with_applied[$parent_path])) {
455
                // If the parent object hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
456
                $this->with($parent_path);
457
            }
458
        }
459
460
        // Add to with_applied to prevent duplicate joins
461
        $this->with_applied[$target_path] = true;
462
463
        // Use the keys of the empty object to determine the columns
464
        $select = array_keys($target->object);
465
        foreach ($select as $i => $column) {
466
            // Add the prefix so that load_result can determine the relationship
467
            $select[$i] = $target_path.'.'.$column.' AS '.$target_path.':'.$column;
468
        }
469
470
471
        // Select all of the prefixed keys in the object
472
        $this->db->select($select);
0 ignored issues
show
Bug introduced by
The method select cannot be called on $this->db (of type string).

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...
473
474
        if (in_array($target->object_name, $parent->belongs_to) or ! isset($target->object[$parent->foreign_key($target_name)])) {
475
            // Parent belongs_to target, use target's primary key as join column
476
            $join_col1 = $target->foreign_key(true, $target_path);
477
            $join_col2 = $parent->foreign_key($target_name, $parent_path);
0 ignored issues
show
Bug introduced by
The variable $parent does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
478
        } else {
479
            // Parent has_one target, use parent's primary key as join column
480
            $join_col2 = $parent->foreign_key(true, $parent_path);
481
            $join_col1 = $parent->foreign_key($target_name, $target_path);
482
        }
483
484
        // This allows for models to use different table prefixes (sharing the same database)
485
        $join_table = new Database_Expression($target->db->table_prefix().$target->table_name.' AS '.$this->db->table_prefix().$target_path);
0 ignored issues
show
Bug introduced by
The method table_prefix cannot be called on $this->db (of type string).

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...
486
487
        // Join the related object into the result
488
        $this->db->join($join_table, $join_col1, $join_col2, 'LEFT');
0 ignored issues
show
Bug introduced by
The method join cannot be called on $this->db (of type string).

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...
489
490
        return $this;
491
    }
492
493
    /**
494
     * Finds and loads a single database row into the object.
495
     *
496
     * @chainable
497
     * @param   mixed  primary key or an array of clauses
498
     * @return  ORM
0 ignored issues
show
Documentation introduced by
Should the return type not be ORM_Iterator|ORM_Core?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
499
     */
500
    public function find($id = null)
501
    {
502
        if ($id !== null) {
503
            if (is_array($id)) {
504
                // Search for all clauses
505
                $this->db->where($id);
0 ignored issues
show
Bug introduced by
The method where cannot be called on $this->db (of type string).

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...
506
            } else {
507
                // Search for a specific column
508
                $this->db->where($this->table_name.'.'.$this->unique_key($id), $id);
0 ignored issues
show
Bug introduced by
The method where cannot be called on $this->db (of type string).

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...
509
            }
510
        }
511
512
        return $this->load_result();
513
    }
514
515
    /**
516
     * Finds multiple database rows and returns an iterator of the rows found.
517
     *
518
     * @chainable
519
     * @param   integer  SQL limit
520
     * @param   integer  SQL offset
521
     * @return  ORM_Iterator
522
     */
523
    public function find_all($limit = null, $offset = null)
524
    {
525
        if ($limit !== null and ! isset($this->db_applied['limit'])) {
526
            // Set limit
527
            $this->limit($limit);
0 ignored issues
show
Documentation Bug introduced by
The method limit does not exist on object<ORM_Core>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
528
        }
529
530
        if ($offset !== null and ! isset($this->db_applied['offset'])) {
531
            // Set offset
532
            $this->offset($offset);
0 ignored issues
show
Documentation Bug introduced by
The method offset does not exist on object<ORM_Core>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
533
        }
534
535
        return $this->load_result(true);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->load_result(true); of type ORM_Iterator|ORM_Core adds the type ORM_Core to the return on line 535 which is incompatible with the return type documented by ORM_Core::find_all of type ORM_Iterator.
Loading history...
536
    }
537
538
    /**
539
     * Creates a key/value array from all of the objects available. Uses find_all
540
     * to find the objects.
541
     *
542
     * @param   string  key column
543
     * @param   string  value column
544
     * @return  array
545
     */
546
    public function select_list($key = null, $val = null)
547
    {
548
        if ($key === null) {
549
            $key = $this->primary_key;
550
        }
551
552
        if ($val === null) {
553
            $val = $this->primary_val;
554
        }
555
556
        // Return a select list from the results
557
        return $this->select($key, $val)->find_all()->select_list($key, $val);
0 ignored issues
show
Bug introduced by
The method select() does not exist on ORM_Core. Did you maybe mean select_list()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
558
    }
559
560
    /**
561
     * Validates the current object. This method should generally be called
562
     * via the model, after the $_POST Validation object has been created.
563
     *
564
     * @param   object   Validation array
565
     * @return  boolean
566
     */
567
    public function validate(Validation $array, $save = false)
568
    {
569
        $safe_array = $array->safe_array();
570
571
        if (! $array->submitted()) {
572
            foreach ($safe_array as $key => $value) {
573
                // Get the value from this object
574
                $value = $this->$key;
575
576
                if (is_object($value) and $value instanceof ORM_Iterator) {
0 ignored issues
show
Bug introduced by
The class ORM_Iterator does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
577
                    // Convert the value to an array of primary keys
578
                    $value = $value->primary_key_array();
579
                }
580
581
                // Pre-fill data
582
                $array[$key] = $value;
583
            }
584
        }
585
586
        // Validate the array
587
        if ($status = $array->validate()) {
588
            // Grab only set fields (excludes missing data, unlike safe_array)
589
            $fields = $array->as_array();
590
591
            foreach ($fields as $key => $value) {
592
                if (isset($safe_array[$key])) {
593
                    // Set new data, ignoring any missing fields or fields without rules
594
                    $this->$key = $value;
595
                }
596
            }
597
598
            if ($save === true or is_string($save)) {
599
                // Save this object
600
                $this->save();
601
602
                if (is_string($save)) {
603
                    // Redirect to the saved page
604
                    url::redirect($save);
605
                }
606
            }
607
        }
608
609
        // Return validation status
610
        return $status;
611
    }
612
613
    /**
614
     * Saves the current object.
615
     *
616
     * @chainable
617
     * @return  ORM_Core
618
     */
619
    public function save()
620
    {
621
        if (! empty($this->changed)) {
622
            $data = array();
623
            foreach ($this->changed as $column) {
624
                // Compile changed data
625
                $data[$column] = $this->object[$column];
626
            }
627
628
            if ($this->loaded === true) {
629
                $query = $this->db
0 ignored issues
show
Bug introduced by
The method where cannot be called on $this->db (of type string).

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...
Unused Code introduced by
$query 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...
630
                    ->where($this->primary_key, $this->object[$this->primary_key])
631
                    ->update($this->table_name, $data);
632
633
                // Object has been saved
634
                $this->saved = true;
635
            } else {
636
                $query = $this->db
0 ignored issues
show
Bug introduced by
The method insert cannot be called on $this->db (of type string).

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...
637
                    ->insert($this->table_name, $data);
638
639
                if ($query->count() > 0) {
640
                    if (empty($this->object[$this->primary_key])) {
641
                        // Load the insert id as the primary key
642
                        $this->object[$this->primary_key] = $query->insert_id();
643
                    }
644
645
                    // Object is now loaded and saved
646
                    $this->loaded = $this->saved = true;
647
                }
648
            }
649
650
            if ($this->saved === true) {
651
                // All changes have been saved
652
                $this->changed = array();
653
            }
654
        }
655
656
        if ($this->saved === true and ! empty($this->changed_relations)) {
657
            foreach ($this->changed_relations as $column => $values) {
658
                // All values that were added
659
                $added = array_diff($values, $this->object_relations[$column]);
660
661
                // All values that were saved
662
                $removed = array_diff($this->object_relations[$column], $values);
663
664
                if (empty($added) and empty($removed)) {
665
                    // No need to bother
666
                    continue;
667
                }
668
669
                // Clear related columns
670
                unset($this->related[$column]);
671
672
                // Load the model
673
                $model = ORM::factory(inflector::singular($column));
674
675
                if (($join_table = array_search($column, $this->has_and_belongs_to_many)) === false) {
676
                    continue;
677
                }
678
679
                if (is_int($join_table)) {
680
                    // No "through" table, load the default JOIN table
681
                    $join_table = $model->join_table($this->table_name);
682
                }
683
684
                // Foreign keys for the join table
685
                $object_fk  = $this->foreign_key(null);
686
                $related_fk = $model->foreign_key(null);
687
688
                if (! empty($added)) {
689
                    foreach ($added as $id) {
690
                        // Insert the new relationship
691
                        $this->db->insert($join_table, array(
0 ignored issues
show
Bug introduced by
The method insert cannot be called on $this->db (of type string).

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...
692
                            $object_fk  => $this->object[$this->primary_key],
693
                            $related_fk => $id,
694
                        ));
695
                    }
696
                }
697
698
                if (! empty($removed)) {
699
                    $this->db
0 ignored issues
show
Bug introduced by
The method where cannot be called on $this->db (of type string).

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...
700
                        ->where($object_fk, $this->object[$this->primary_key])
701
                        ->in($related_fk, $removed)
702
                        ->delete($join_table);
703
                }
704
705
                // Clear all relations for this column
706
                unset($this->object_relations[$column], $this->changed_relations[$column]);
707
            }
708
        }
709
710
        return $this;
711
    }
712
713
    /**
714
     * Deletes the current object from the database. This does NOT destroy
715
     * relationships that have been created with other objects.
716
     *
717
     * @chainable
718
     * @return  ORM_Core
719
     */
720
    public function delete($id = null)
721
    {
722
        if ($id === null and $this->loaded) {
723
            // Use the the primary key value
724
            $id = $this->object[$this->primary_key];
725
        }
726
727
        // Delete this object
728
        $this->db->where($this->primary_key, $id)->delete($this->table_name);
0 ignored issues
show
Bug introduced by
The method where cannot be called on $this->db (of type string).

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...
729
730
        return $this->clear();
731
    }
732
733
    /**
734
     * Delete all objects in the associated table. This does NOT destroy
735
     * relationships that have been created with other objects.
736
     *
737
     * @chainable
738
     * @param   array  ids to delete
739
     * @return  ORM_Core
740
     */
741
    public function delete_all($ids = null)
742
    {
743
        if (is_array($ids)) {
744
            // Delete only given ids
745
            $this->db->in($this->primary_key, $ids);
0 ignored issues
show
Bug introduced by
The method in cannot be called on $this->db (of type string).

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...
746
        } elseif (is_null($ids)) {
747
            // Delete all records
748
            $this->db->where('1=1');
0 ignored issues
show
Bug introduced by
The method where cannot be called on $this->db (of type string).

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...
749
        } else {
750
            // Do nothing - safeguard
751
            return $this;
752
        }
753
754
        // Delete all objects
755
        $this->db->delete($this->table_name);
0 ignored issues
show
Bug introduced by
The method delete cannot be called on $this->db (of type string).

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...
756
757
        return $this->clear();
758
    }
759
760
    /**
761
     * Unloads the current object and clears the status.
762
     *
763
     * @chainable
764
     * @return  ORM_Core
765
     */
766
    public function clear()
767
    {
768
        // Create an array with all the columns set to NULL
769
        $columns = array_keys($this->table_columns);
770
        $values  = array_combine($columns, array_fill(0, count($columns), null));
771
772
        // Replace the current object with an empty one
773
        $this->load_values($values);
774
775
        return $this;
776
    }
777
778
    /**
779
     * Reloads the current object from the database.
780
     *
781
     * @chainable
782
     * @return  ORM
0 ignored issues
show
Documentation introduced by
Should the return type not be ORM_Iterator|ORM_Core?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
783
     */
784
    public function reload()
785
    {
786
        return $this->find($this->object[$this->primary_key]);
787
    }
788
789
    /**
790
     * Reload column definitions.
791
     *
792
     * @chainable
793
     * @param   boolean  force reloading
794
     * @return  ORM_Core
795
     */
796
    public function reload_columns($force = false)
797
    {
798
        if ($force === true or empty($this->table_columns)) {
799
            if (isset(ORM::$column_cache[$this->object_name])) {
800
                // Use cached column information
801
                $this->table_columns = ORM::$column_cache[$this->object_name];
802
            } else {
803
                // Load table columns
804
                ORM::$column_cache[$this->object_name] = $this->table_columns = $this->list_fields();
805
            }
806
        }
807
808
        return $this;
809
    }
810
811
    /**
812
     * Tests if this object has a relationship to a different model.
813
     *
814
     * @param   object   related ORM model
815
     * @param   boolean  check for any relations to given model
816
     * @return  boolean
817
     */
818
    public function has(ORM $model, $any = false)
819
    {
820
        // Determine plural or singular relation name
821
        $related = ($model->table_names_plural === true) ? $model->object_plural : $model->object_name;
822
823
        if (($join_table = array_search($related, $this->has_and_belongs_to_many)) === false) {
824
            return false;
825
        }
826
827
        if (is_int($join_table)) {
828
            // No "through" table, load the default JOIN table
829
            $join_table = $model->join_table($this->table_name);
830
        }
831
832
        if (! isset($this->object_relations[$related])) {
833
            // Load the object relationships
834
            $this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model);
835
        }
836
837
        if (! $model->empty_primary_key()) {
838
            // Check if a specific object exists
839
            return in_array($model->primary_key_value, $this->changed_relations[$related]);
840
        } elseif ($any) {
841
            // Check if any relations to given model exist
842
            return ! empty($this->changed_relations[$related]);
843
        } else {
844
            return false;
845
        }
846
    }
847
848
    /**
849
     * Adds a new relationship to between this model and another.
850
     *
851
     * @param   object   related ORM model
852
     * @return  boolean
853
     */
854
    public function add(ORM $model)
855
    {
856
        if ($this->has($model)) {
857
            return true;
858
        }
859
860
        // Get the faked column name
861
        $column = $model->object_plural;
862
863
        // Add the new relation to the update
864
        $this->changed_relations[$column][] = $model->primary_key_value;
865
866
        if (isset($this->related[$column])) {
867
            // Force a reload of the relationships
868
            unset($this->related[$column]);
869
        }
870
871
        return true;
872
    }
873
874
    /**
875
     * Adds a new relationship to between this model and another.
876
     *
877
     * @param   object   related ORM model
878
     * @return  boolean
879
     */
880
    public function remove(ORM $model)
881
    {
882
        if (! $this->has($model)) {
883
            return false;
884
        }
885
886
        // Get the faked column name
887
        $column = $model->object_plural;
888
889
        if (($key = array_search($model->primary_key_value, $this->changed_relations[$column])) === false) {
890
            return false;
891
        }
892
893
        // Remove the relationship
894
        unset($this->changed_relations[$column][$key]);
895
896
        if (isset($this->related[$column])) {
897
            // Force a reload of the relationships
898
            unset($this->related[$column]);
899
        }
900
901
        return true;
902
    }
903
904
    /**
905
     * Count the number of records in the table.
906
     *
907
     * @return  integer
908
     */
909
    public function count_all()
910
    {
911
        // Return the total number of records in a table
912
        return $this->db->count_records($this->table_name);
0 ignored issues
show
Bug introduced by
The method count_records cannot be called on $this->db (of type string).

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...
913
    }
914
915
    /**
916
     * Proxy method to Database list_fields.
917
     *
918
     * @param   string  table name or NULL to use this table
919
     * @return  array
920
     */
921
    public function list_fields($table = null)
922
    {
923
        if ($table === null) {
924
            $table = $this->table_name;
925
        }
926
927
        // Proxy to database
928
        return $this->db->list_fields($table);
0 ignored issues
show
Bug introduced by
The method list_fields cannot be called on $this->db (of type string).

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...
929
    }
930
931
    /**
932
     * Proxy method to Database field_data.
933
     *
934
     * @param   string  table name
935
     * @return  array
936
     */
937
    public function field_data($table)
938
    {
939
        // Proxy to database
940
        return $this->db->field_data($table);
0 ignored issues
show
Bug introduced by
The method field_data cannot be called on $this->db (of type string).

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...
941
    }
942
943
    /**
944
     * Proxy method to Database field_data.
945
     *
946
     * @chainable
947
     * @param   string  SQL query to clear
948
     * @return  ORM_Core
949
     */
950
    public function clear_cache($sql = null)
951
    {
952
        // Proxy to database
953
        $this->db->clear_cache($sql);
0 ignored issues
show
Bug introduced by
The method clear_cache cannot be called on $this->db (of type string).

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...
954
955
        ORM::$column_cache = array();
956
957
        return $this;
958
    }
959
960
    /**
961
     * Returns the unique key for a specific value. This method is expected
962
     * to be overloaded in models if the model has other unique columns.
963
     *
964
     * @param   mixed   unique value
965
     * @return  string
966
     */
967
    public function unique_key($id)
0 ignored issues
show
Unused Code introduced by
The parameter $id 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...
968
    {
969
        return $this->primary_key;
970
    }
971
972
    /**
973
     * Determines the name of a foreign key for a specific table.
974
     *
975
     * @param   string  related table name
976
     * @param   string  prefix table name (used for JOINs)
977
     * @return  string
978
     */
979
    public function foreign_key($table = null, $prefix_table = null)
980
    {
981
        if ($table === true) {
982
            if (is_string($prefix_table)) {
983
                // Use prefix table name and this table's PK
984
                return $prefix_table.'.'.$this->primary_key;
985
            } else {
986
                // Return the name of this table's PK
987
                return $this->table_name.'.'.$this->primary_key;
988
            }
989
        }
990
991
        if (is_string($prefix_table)) {
992
            // Add a period for prefix_table.column support
993
            $prefix_table .= '.';
994
        }
995
996
        if (isset($this->foreign_key[$table])) {
997
            // Use the defined foreign key name, no magic here!
998
            $foreign_key = $this->foreign_key[$table];
999
        } else {
1000
            if (! is_string($table) or ! array_key_exists($table.'_'.$this->primary_key, $this->object)) {
1001
                // Use this table
1002
                $table = $this->table_name;
1003
1004
                if (strpos($table, '.') !== false) {
1005
                    // Hack around support for PostgreSQL schemas
1006
                    list($schema, $table) = explode('.', $table, 2);
0 ignored issues
show
Unused Code introduced by
The assignment to $schema is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1007
                }
1008
1009
                if ($this->table_names_plural === true) {
1010
                    // Make the key name singular
1011
                    $table = inflector::singular($table);
1012
                }
1013
            }
1014
1015
            $foreign_key = $table.'_'.$this->primary_key;
1016
        }
1017
1018
        return $prefix_table.$foreign_key;
1019
    }
1020
1021
    /**
1022
     * This uses alphabetical comparison to choose the name of the table.
1023
     *
1024
     * Example: The joining table of users and roles would be roles_users,
1025
     * because "r" comes before "u". Joining products and categories would
1026
     * result in categories_products, because "c" comes before "p".
1027
     *
1028
     * Example: zoo > zebra > robber > ocean > angel > aardvark
1029
     *
1030
     * @param   string  table name
1031
     * @return  string
1032
     */
1033
    public function join_table($table)
1034
    {
1035
        if ($this->table_name > $table) {
1036
            $table = $table.'_'.$this->table_name;
1037
        } else {
1038
            $table = $this->table_name.'_'.$table;
1039
        }
1040
1041
        return $table;
1042
    }
1043
1044
    /**
1045
     * Returns an ORM model for the given object name;
1046
     *
1047
     * @param   string  object name
1048
     * @return  ORM
1049
     */
1050
    protected function related_object($object)
1051
    {
1052
        if (isset($this->has_one[$object])) {
1053
            $object = ORM::factory($this->has_one[$object]);
1054
        } elseif (isset($this->belongs_to[$object])) {
1055
            $object = ORM::factory($this->belongs_to[$object]);
1056
        } elseif (in_array($object, $this->has_one) or in_array($object, $this->belongs_to)) {
1057
            $object = ORM::factory($object);
1058
        } else {
1059
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by ORM_Core::related_object of type ORM.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1060
        }
1061
1062
        return $object;
1063
    }
1064
1065
    /**
1066
     * Loads an array of values into into the current object.
1067
     *
1068
     * @chainable
1069
     * @param   array  values to load
1070
     * @return  ORM_Core
1071
     */
1072
    public function load_values(array $values)
1073
    {
1074
        if (array_key_exists($this->primary_key, $values)) {
1075
            // Replace the object and reset the object status
1076
            $this->object = $this->changed = $this->related = array();
1077
1078
            // Set the loaded and saved object status based on the primary key
1079
            $this->loaded = $this->saved = ($values[$this->primary_key] !== null);
1080
        }
1081
1082
        // Related objects
1083
        $related = array();
1084
1085
        foreach ($values as $column => $value) {
1086
            if (strpos($column, ':') === false) {
1087
                if (isset($this->table_columns[$column])) {
1088
                    // The type of the value can be determined, convert the value
1089
                    $value = $this->load_type($column, $value);
1090
                }
1091
1092
                $this->object[$column] = $value;
1093
            } else {
1094
                list($prefix, $column) = explode(':', $column, 2);
1095
1096
                $related[$prefix][$column] = $value;
1097
            }
1098
        }
1099
1100
        if (! empty($related)) {
1101
            foreach ($related as $object => $values) {
1102
                // Load the related objects with the values in the result
1103
                $this->related[$object] = $this->related_object($object)->load_values($values);
1104
            }
1105
        }
1106
1107
        return $this;
1108
    }
1109
1110
    /**
1111
     * Loads a value according to the types defined by the column metadata.
1112
     *
1113
     * @param   string  column name
1114
     * @param   mixed   value to load
1115
     * @return  mixed
1116
     */
1117
    protected function load_type($column, $value)
1118
    {
1119
        $type = gettype($value);
1120
        if ($type == 'object' or $type == 'array' or ! isset($this->table_columns[$column])) {
1121
            return $value;
1122
        }
1123
1124
        // Load column data
1125
        $column = $this->table_columns[$column];
1126
1127
        if ($value === null and ! empty($column['null'])) {
1128
            return $value;
1129
        }
1130
1131
        if (! empty($column['binary']) and ! empty($column['exact']) and (int) $column['length'] === 1) {
1132
            // Use boolean for BINARY(1) fields
1133
            $column['type'] = 'boolean';
1134
        }
1135
1136
        switch ($column['type']) {
1137
            case 'int':
1138
                if ($value === '' and ! empty($column['null'])) {
1139
                    // Forms will only submit strings, so empty integer values must be null
1140
                    $value = null;
1141
                } elseif ((float) $value > PHP_INT_MAX) {
1142
                    // This number cannot be represented by a PHP integer, so we convert it to a string
1143
                    $value = (string) $value;
1144
                } else {
1145
                    $value = (int) $value;
1146
                }
1147
            break;
1148
            case 'float':
1149
                $value = (float) $value;
1150
            break;
1151
            case 'boolean':
1152
                $value = (bool) $value;
1153
            break;
1154
            case 'string':
1155
                $value = (string) $value;
1156
            break;
1157
        }
1158
1159
        return $value;
1160
    }
1161
1162
    /**
1163
     * Loads a database result, either as a new object for this model, or as
1164
     * an iterator for multiple rows.
1165
     *
1166
     * @chainable
1167
     * @param   boolean       return an iterator or load a single row
1168
     * @return  ORM           for single rows
1169
     * @return  ORM_Iterator  for multiple rows
0 ignored issues
show
Documentation introduced by
Should the return type not be ORM_Iterator|ORM_Core?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1170
     */
1171
    protected function load_result($array = false)
1172
    {
1173
        if ($array === false) {
1174
            // Only fetch 1 record
1175
            $this->db->limit(1);
0 ignored issues
show
Bug introduced by
The method limit cannot be called on $this->db (of type string).

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...
1176
        }
1177
1178
        if (! isset($this->db_applied['select'])) {
1179
            // Select all columns by default
1180
            $this->db->select($this->table_name.'.*');
0 ignored issues
show
Bug introduced by
The method select cannot be called on $this->db (of type string).

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...
1181
        }
1182
1183
        if (! empty($this->load_with)) {
1184
            foreach ($this->load_with as $alias => $object) {
1185
                // Join each object into the results
1186
                if (is_string($alias)) {
1187
                    // Use alias
1188
                    $this->with($alias);
1189
                } else {
1190
                    // Use object
1191
                    $this->with($object);
1192
                }
1193
            }
1194
        }
1195
1196
        if (! isset($this->db_applied['orderby']) and ! empty($this->sorting)) {
1197
            $sorting = array();
1198
            foreach ($this->sorting as $column => $direction) {
1199
                if (strpos($column, '.') === false) {
1200
                    // Keeps sorting working properly when using JOINs on
1201
                    // tables with columns of the same name
1202
                    $column = $this->table_name.'.'.$column;
1203
                }
1204
1205
                $sorting[$column] = $direction;
1206
            }
1207
1208
            // Apply the user-defined sorting
1209
            $this->db->orderby($sorting);
0 ignored issues
show
Bug introduced by
The method orderby cannot be called on $this->db (of type string).

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...
1210
        }
1211
1212
        // Load the result
1213
        $result = $this->db->get($this->table_name);
0 ignored issues
show
Bug introduced by
The method get cannot be called on $this->db (of type string).

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...
1214
1215
        if ($array === true) {
1216
            // Return an iterated result
1217
            return new ORM_Iterator($this, $result);
1218
        }
1219
1220
        if ($result->count() === 1) {
1221
            // Load object values
1222
            $this->load_values($result->result(false)->current());
1223
        } else {
1224
            // Clear the object, nothing was found
1225
            $this->clear();
1226
        }
1227
1228
        return $this;
1229
    }
1230
1231
    /**
1232
     * Return an array of all the primary keys of the related table.
1233
     *
1234
     * @param   string  table name
1235
     * @param   object  ORM model to find relations of
1236
     * @return  array
1237
     */
1238
    protected function load_relations($table, ORM $model)
1239
    {
1240
        // Save the current query chain (otherwise the next call will clash)
1241
        $this->db->push();
0 ignored issues
show
Bug introduced by
The method push cannot be called on $this->db (of type string).

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...
1242
1243
        $query = $this->db
0 ignored issues
show
Bug introduced by
The method select cannot be called on $this->db (of type string).

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...
1244
            ->select($model->foreign_key(null).' AS id')
1245
            ->from($table)
1246
            ->where($this->foreign_key(null, $table), $this->object[$this->primary_key])
1247
            ->get()
1248
            ->result(true);
1249
1250
        $this->db->pop();
0 ignored issues
show
Bug introduced by
The method pop cannot be called on $this->db (of type string).

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...
1251
1252
        $relations = array();
1253
        foreach ($query as $row) {
1254
            $relations[] = $row->id;
1255
        }
1256
1257
        return $relations;
1258
    }
1259
1260
    /**
1261
     * Returns whether or not primary key is empty
1262
     *
1263
     * @return bool
1264
     */
1265
    protected function empty_primary_key()
1266
    {
1267
        return (empty($this->object[$this->primary_key]) and $this->object[$this->primary_key] !== '0');
1268
    }
1269
} // End ORM
1270