• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 20589198684

30 Dec 2025 04:55AM UTC coverage: 84.503% (-0.03%) from 84.53%
20589198684

Pull #9853

github

web-flow
Merge f1d8312ec into e2fc5243b
Pull Request #9853: feat(encryption): Add previous keys fallback feature

65 of 77 new or added lines in 5 files covered. (84.42%)

38 existing lines in 3 files now uncovered.

21572 of 25528 relevant lines covered (84.5%)

203.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.4
/system/Model.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter;
15

16
use Closure;
17
use CodeIgniter\Database\BaseBuilder;
18
use CodeIgniter\Database\BaseConnection;
19
use CodeIgniter\Database\ConnectionInterface;
20
use CodeIgniter\Database\Exceptions\DatabaseException;
21
use CodeIgniter\Database\Exceptions\DataException;
22
use CodeIgniter\Entity\Entity;
23
use CodeIgniter\Exceptions\BadMethodCallException;
24
use CodeIgniter\Exceptions\ModelException;
25
use CodeIgniter\Validation\ValidationInterface;
26
use Config\Database;
27
use Config\Feature;
28
use stdClass;
29

30
/**
31
 * The Model class extends BaseModel and provides additional
32
 * convenient features that makes working with a SQL database
33
 * table less painful.
34
 *
35
 * It will:
36
 *      - automatically connect to database
37
 *      - allow intermingling calls to the builder
38
 *      - removes the need to use Result object directly in most cases
39
 *
40
 * @property-read BaseConnection $db
41
 *
42
 * @method $this groupBy($by, ?bool $escape = null)
43
 * @method $this groupEnd()
44
 * @method $this groupStart()
45
 * @method $this having($key, $value = null, ?bool $escape = null)
46
 * @method $this havingGroupEnd()
47
 * @method $this havingGroupStart()
48
 * @method $this havingIn(?string $key = null, $values = null, ?bool $escape = null)
49
 * @method $this havingLike($field, string $match = '', string $side = 'both', ?bool $escape = null, bool $insensitiveSearch = false)
50
 * @method $this havingNotIn(?string $key = null, $values = null, ?bool $escape = null)
51
 * @method $this join(string $table, string $cond, string $type = '', ?bool $escape = null)
52
 * @method $this like($field, string $match = '', string $side = 'both', ?bool $escape = null, bool $insensitiveSearch = false)
53
 * @method $this limit(?int $value = null, ?int $offset = 0)
54
 * @method $this notGroupStart()
55
 * @method $this notHavingGroupStart()
56
 * @method $this notHavingLike($field, string $match = '', string $side = 'both', ?bool $escape = null, bool $insensitiveSearch = false)
57
 * @method $this notLike($field, string $match = '', string $side = 'both', ?bool $escape = null, bool $insensitiveSearch = false)
58
 * @method $this offset(int $offset)
59
 * @method $this orderBy(string $orderBy, string $direction = '', ?bool $escape = null)
60
 * @method $this orGroupStart()
61
 * @method $this orHaving($key, $value = null, ?bool $escape = null)
62
 * @method $this orHavingGroupStart()
63
 * @method $this orHavingIn(?string $key = null, $values = null, ?bool $escape = null)
64
 * @method $this orHavingLike($field, string $match = '', string $side = 'both', ?bool $escape = null, bool $insensitiveSearch = false)
65
 * @method $this orHavingNotIn(?string $key = null, $values = null, ?bool $escape = null)
66
 * @method $this orLike($field, string $match = '', string $side = 'both', ?bool $escape = null, bool $insensitiveSearch = false)
67
 * @method $this orNotGroupStart()
68
 * @method $this orNotHavingGroupStart()
69
 * @method $this orNotHavingLike($field, string $match = '', string $side = 'both', ?bool $escape = null, bool $insensitiveSearch = false)
70
 * @method $this orNotLike($field, string $match = '', string $side = 'both', ?bool $escape = null, bool $insensitiveSearch = false)
71
 * @method $this orWhere($key, $value = null, ?bool $escape = null)
72
 * @method $this orWhereIn(?string $key = null, $values = null, ?bool $escape = null)
73
 * @method $this orWhereNotIn(?string $key = null, $values = null, ?bool $escape = null)
74
 * @method $this select($select = '*', ?bool $escape = null)
75
 * @method $this selectAvg(string $select = '', string $alias = '')
76
 * @method $this selectCount(string $select = '', string $alias = '')
77
 * @method $this selectMax(string $select = '', string $alias = '')
78
 * @method $this selectMin(string $select = '', string $alias = '')
79
 * @method $this selectSum(string $select = '', string $alias = '')
80
 * @method $this when($condition, callable $callback, ?callable $defaultCallback = null)
81
 * @method $this whenNot($condition, callable $callback, ?callable $defaultCallback = null)
82
 * @method $this where($key, $value = null, ?bool $escape = null)
83
 * @method $this whereIn(?string $key = null, $values = null, ?bool $escape = null)
84
 * @method $this whereNotIn(?string $key = null, $values = null, ?bool $escape = null)
85
 *
86
 * @phpstan-import-type row_array from BaseModel
87
 */
88
class Model extends BaseModel
89
{
90
    /**
91
     * Name of database table.
92
     *
93
     * @var string
94
     */
95
    protected $table;
96

97
    /**
98
     * The table's primary key.
99
     *
100
     * @var string
101
     */
102
    protected $primaryKey = 'id';
103

104
    /**
105
     * Whether primary key uses auto increment.
106
     *
107
     * @var bool
108
     */
109
    protected $useAutoIncrement = true;
110

111
    /**
112
     * Query Builder object.
113
     *
114
     * @var BaseBuilder|null
115
     */
116
    protected $builder;
117

118
    /**
119
     * Holds information passed in via 'set'
120
     * so that we can capture it (not the builder)
121
     * and ensure it gets validated first.
122
     *
123
     * @var array{escape: array<int|string, bool|null>, data: row_array}|array{}
124
     */
125
    protected $tempData = [];
126

127
    /**
128
     * Escape array that maps usage of escape
129
     * flag for every parameter.
130
     *
131
     * @var array<int|string, bool|null>
132
     */
133
    protected $escape = [];
134

135
    /**
136
     * Builder method names that should not be used in the Model.
137
     *
138
     * @var list<string>
139
     */
140
    private array $builderMethodsNotAvailable = [
141
        'getCompiledInsert',
142
        'getCompiledSelect',
143
        'getCompiledUpdate',
144
    ];
145

146
    public function __construct(?ConnectionInterface $db = null, ?ValidationInterface $validation = null)
147
    {
148
        /**
149
         * @var BaseConnection|null $db
150
         */
151
        $db ??= Database::connect($this->DBGroup);
391✔
152

153
        $this->db = $db;
391✔
154

155
        parent::__construct($validation);
391✔
156
    }
157

158
    /**
159
     * Specify the table associated with a model.
160
     *
161
     * @return $this
162
     */
163
    public function setTable(string $table)
164
    {
165
        $this->table = $table;
1✔
166

167
        return $this;
1✔
168
    }
169

170
    protected function doFind(bool $singleton, $id = null)
171
    {
172
        $builder = $this->builder();
56✔
173
        $useCast = $this->useCasts();
55✔
174

175
        if ($useCast) {
55✔
176
            $returnType = $this->tempReturnType;
18✔
177
            $this->asArray();
18✔
178
        }
179

180
        if ($this->tempUseSoftDeletes) {
55✔
181
            $builder->where($this->table . '.' . $this->deletedField, null);
31✔
182
        }
183

184
        $row  = null;
55✔
185
        $rows = [];
55✔
186

187
        if (is_array($id)) {
55✔
188
            $rows = $builder->whereIn($this->table . '.' . $this->primaryKey, $id)
3✔
189
                ->get()
3✔
190
                ->getResult($this->tempReturnType);
3✔
191
        } elseif ($singleton) {
52✔
192
            $row = $builder->where($this->table . '.' . $this->primaryKey, $id)
44✔
193
                ->get()
44✔
194
                ->getFirstRow($this->tempReturnType);
44✔
195
        } else {
196
            $rows = $builder->get()->getResult($this->tempReturnType);
9✔
197
        }
198

199
        if ($useCast) {
55✔
200
            $this->tempReturnType = $returnType;
18✔
201

202
            if ($singleton) {
18✔
203
                if ($row === null) {
15✔
204
                    return null;
1✔
205
                }
206

207
                return $this->convertToReturnType($row, $returnType);
14✔
208
            }
209

210
            foreach ($rows as $i => $row) {
3✔
211
                $rows[$i] = $this->convertToReturnType($row, $returnType);
3✔
212
            }
213

214
            return $rows;
3✔
215
        }
216

217
        if ($singleton) {
37✔
218
            return $row;
29✔
219
        }
220

221
        return $rows;
9✔
222
    }
223

224
    protected function doFindColumn(string $columnName)
225
    {
226
        return $this->select($columnName)->asArray()->find();
2✔
227
    }
228

229
    /**
230
     * {@inheritDoc}
231
     *
232
     * Works with the current Query Builder instance.
233
     */
234
    protected function doFindAll(?int $limit = null, int $offset = 0)
235
    {
236
        $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true;
22✔
237
        if ($limitZeroAsAll) {
22✔
238
            $limit ??= 0;
22✔
239
        }
240

241
        $builder = $this->builder();
22✔
242

243
        $useCast = $this->useCasts();
22✔
244
        if ($useCast) {
22✔
245
            $returnType = $this->tempReturnType;
5✔
246
            $this->asArray();
5✔
247
        }
248

249
        if ($this->tempUseSoftDeletes) {
22✔
250
            $builder->where($this->table . '.' . $this->deletedField, null);
10✔
251
        }
252

253
        $results = $builder->limit($limit, $offset)
22✔
254
            ->get()
22✔
255
            ->getResult($this->tempReturnType);
22✔
256

257
        if ($useCast) {
22✔
258
            foreach ($results as $i => $row) {
5✔
259
                $results[$i] = $this->convertToReturnType($row, $returnType);
4✔
260
            }
261

262
            $this->tempReturnType = $returnType;
5✔
263
        }
264

265
        return $results;
22✔
266
    }
267

268
    /**
269
     * {@inheritDoc}
270
     *
271
     * Will take any previous Query Builder calls into account
272
     * when determining the result set.
273
     */
274
    protected function doFirst()
275
    {
276
        $builder = $this->builder();
24✔
277

278
        $useCast = $this->useCasts();
24✔
279
        if ($useCast) {
24✔
280
            $returnType = $this->tempReturnType;
5✔
281
            $this->asArray();
5✔
282
        }
283

284
        if ($this->tempUseSoftDeletes) {
24✔
285
            $builder->where($this->table . '.' . $this->deletedField, null);
19✔
286
        } elseif ($this->useSoftDeletes && ($builder->QBGroupBy === []) && $this->primaryKey !== '') {
13✔
287
            $builder->groupBy($this->table . '.' . $this->primaryKey);
6✔
288
        }
289

290
        // Some databases, like PostgreSQL, need order
291
        // information to consistently return correct results.
292
        if ($builder->QBGroupBy !== [] && ($builder->QBOrderBy === []) && $this->primaryKey !== '') {
24✔
293
            $builder->orderBy($this->table . '.' . $this->primaryKey, 'asc');
9✔
294
        }
295

296
        $row = $builder->limit(1, 0)->get()->getFirstRow($this->tempReturnType);
24✔
297

298
        if ($useCast && $row !== null) {
24✔
299
            $row = $this->convertToReturnType($row, $returnType);
4✔
300

301
            $this->tempReturnType = $returnType;
4✔
302
        }
303

304
        return $row;
24✔
305
    }
306

307
    protected function doInsert(array $row)
308
    {
309
        $escape       = $this->escape;
96✔
310
        $this->escape = [];
96✔
311

312
        // Require non-empty primaryKey when
313
        // not using auto-increment feature
314
        if (! $this->useAutoIncrement) {
96✔
315
            if (! isset($row[$this->primaryKey])) {
15✔
316
                throw DataException::forEmptyPrimaryKey('insert');
2✔
317
            }
318

319
            // Validate the primary key value (arrays not allowed for insert)
320
            $this->validateID($row[$this->primaryKey], false);
13✔
321
        }
322

323
        $builder = $this->builder();
85✔
324

325
        // Must use the set() method to ensure to set the correct escape flag
326
        foreach ($row as $key => $val) {
85✔
327
            $builder->set($key, $val, $escape[$key] ?? null);
84✔
328
        }
329

330
        if ($this->allowEmptyInserts && $row === []) {
85✔
331
            $table = $this->db->protectIdentifiers($this->table, true, null, false);
1✔
332
            if ($this->db->getPlatform() === 'MySQLi') {
1✔
333
                $sql = 'INSERT INTO ' . $table . ' VALUES ()';
1✔
334
            } elseif ($this->db->getPlatform() === 'OCI8') {
1✔
335
                $allFields = $this->db->protectIdentifiers(
1✔
336
                    array_map(
1✔
337
                        static fn ($row) => $row->name,
1✔
338
                        $this->db->getFieldData($this->table),
1✔
339
                    ),
1✔
340
                    false,
1✔
341
                    true,
1✔
342
                );
1✔
343

344
                $sql = sprintf(
1✔
345
                    'INSERT INTO %s (%s) VALUES (%s)',
1✔
346
                    $table,
1✔
347
                    implode(',', $allFields),
1✔
348
                    substr(str_repeat(',DEFAULT', count($allFields)), 1),
1✔
349
                );
1✔
350
            } else {
351
                $sql = 'INSERT INTO ' . $table . ' DEFAULT VALUES';
1✔
352
            }
353

354
            $result = $this->db->query($sql);
1✔
355
        } else {
356
            $result = $builder->insert();
84✔
357
        }
358

359
        // If insertion succeeded then save the insert ID
360
        if ($result) {
85✔
361
            $this->insertID = $this->useAutoIncrement ? $this->db->insertID() : $row[$this->primaryKey];
83✔
362
        }
363

364
        return $result;
85✔
365
    }
366

367
    protected function doInsertBatch(?array $set = null, ?bool $escape = null, int $batchSize = 100, bool $testing = false)
368
    {
369
        if (is_array($set) && ! $this->useAutoIncrement) {
21✔
370
            foreach ($set as $row) {
11✔
371
                // Require non-empty $primaryKey when
372
                // not using auto-increment feature
373
                if (! isset($row[$this->primaryKey])) {
11✔
374
                    throw DataException::forEmptyPrimaryKey('insertBatch');
1✔
375
                }
376

377
                // Validate the primary key value
378
                $this->validateID($row[$this->primaryKey], false);
11✔
379
            }
380
        }
381

382
        return $this->builder()->testMode($testing)->insertBatch($set, $escape, $batchSize);
11✔
383
    }
384

385
    protected function doUpdate($id = null, $row = null): bool
386
    {
387
        $escape       = $this->escape;
38✔
388
        $this->escape = [];
38✔
389

390
        $builder = $this->builder();
38✔
391

392
        if (is_array($id) && $id !== []) {
38✔
393
            $builder = $builder->whereIn($this->table . '.' . $this->primaryKey, $id);
32✔
394
        }
395

396
        // Must use the set() method to ensure to set the correct escape flag
397
        foreach ($row as $key => $val) {
38✔
398
            $builder->set($key, $val, $escape[$key] ?? null);
38✔
399
        }
400

401
        if ($builder->getCompiledQBWhere() === []) {
38✔
402
            throw new DatabaseException(
1✔
403
                'Updates are not allowed unless they contain a "where" or "like" clause.',
1✔
404
            );
1✔
405
        }
406

407
        return $builder->update();
37✔
408
    }
409

410
    protected function doUpdateBatch(?array $set = null, ?string $index = null, int $batchSize = 100, bool $returnSQL = false)
411
    {
412
        return $this->builder()->testMode($returnSQL)->updateBatch($set, $index, $batchSize);
4✔
413
    }
414

415
    protected function doDelete($id = null, bool $purge = false)
416
    {
417
        $set     = [];
36✔
418
        $builder = $this->builder();
36✔
419

420
        if (is_array($id) && $id !== []) {
36✔
421
            $builder = $builder->whereIn($this->primaryKey, $id);
22✔
422
        }
423

424
        if ($this->useSoftDeletes && ! $purge) {
36✔
425
            if ($builder->getCompiledQBWhere() === []) {
25✔
426
                throw new DatabaseException(
3✔
427
                    'Deletes are not allowed unless they contain a "where" or "like" clause.',
3✔
428
                );
3✔
429
            }
430

431
            $builder->where($this->deletedField);
22✔
432

433
            $set[$this->deletedField] = $this->setDate();
22✔
434

435
            if ($this->useTimestamps && $this->updatedField !== '') {
21✔
436
                $set[$this->updatedField] = $this->setDate();
1✔
437
            }
438

439
            return $builder->update($set);
21✔
440
        }
441

442
        return $builder->delete();
11✔
443
    }
444

445
    protected function doPurgeDeleted()
446
    {
447
        return $this->builder()
1✔
448
            ->where($this->table . '.' . $this->deletedField . ' IS NOT NULL')
1✔
449
            ->delete();
1✔
450
    }
451

452
    protected function doOnlyDeleted()
453
    {
454
        $this->builder()->where($this->table . '.' . $this->deletedField . ' IS NOT NULL');
1✔
455
    }
456

457
    protected function doReplace(?array $row = null, bool $returnSQL = false)
458
    {
459
        return $this->builder()->testMode($returnSQL)->replace($row);
2✔
460
    }
461

462
    /**
463
     * {@inheritDoc}
464
     *
465
     * The return array should be in the following format:
466
     *  `['source' => 'message']`.
467
     * This method works only with dbCalls.
468
     */
469
    protected function doErrors()
470
    {
471
        // $error is always ['code' => string|int, 'message' => string]
472
        $error = $this->db->error();
2✔
473

474
        if ((int) $error['code'] === 0) {
2✔
475
            return [];
2✔
476
        }
477

UNCOV
478
        return [$this->db::class => $error['message']];
×
479
    }
480

481
    public function getIdValue($row)
482
    {
483
        if (is_object($row)) {
27✔
484
            // Get the raw or mapped primary key value of the Entity.
485
            if ($row instanceof Entity && $row->{$this->primaryKey} !== null) {
17✔
486
                $cast = $row->cast();
9✔
487

488
                // Disable Entity casting, because raw primary key value is needed for database.
489
                $row->cast(false);
9✔
490

491
                $primaryKey = $row->{$this->primaryKey};
9✔
492

493
                // Restore Entity casting setting.
494
                $row->cast($cast);
9✔
495

496
                return $primaryKey;
9✔
497
            }
498

499
            if (! $row instanceof Entity && isset($row->{$this->primaryKey})) {
9✔
500
                return $row->{$this->primaryKey};
5✔
501
            }
502
        }
503

504
        if (is_array($row) && isset($row[$this->primaryKey])) {
15✔
505
            return $row[$this->primaryKey];
4✔
506
        }
507

508
        return null;
11✔
509
    }
510

511
    public function countAllResults(bool $reset = true, bool $test = false)
512
    {
513
        if ($this->tempUseSoftDeletes) {
17✔
514
            $this->builder()->where($this->table . '.' . $this->deletedField, null);
6✔
515
        }
516

517
        // When $reset === false, the $tempUseSoftDeletes will be
518
        // dependent on $useSoftDeletes value because we don't
519
        // want to add the same "where" condition for the second time.
520
        $this->tempUseSoftDeletes = $reset
17✔
521
            ? $this->useSoftDeletes
10✔
522
            : ($this->useSoftDeletes ? false : $this->useSoftDeletes);
12✔
523

524
        return $this->builder()->testMode($test)->countAllResults($reset);
17✔
525
    }
526

527
    /**
528
     * {@inheritDoc}
529
     *
530
     * Works with `$this->builder` to get the Compiled select to
531
     * determine the rows to operate on.
532
     * This method works only with dbCalls.
533
     */
534
    public function chunk(int $size, Closure $userFunc)
535
    {
536
        $total  = $this->builder()->countAllResults(false);
1✔
537
        $offset = 0;
1✔
538

539
        while ($offset <= $total) {
1✔
540
            $builder = clone $this->builder();
1✔
541
            $rows    = $builder->get($size, $offset);
1✔
542

543
            if (! $rows) {
1✔
UNCOV
544
                throw DataException::forEmptyDataset('chunk');
×
545
            }
546

547
            $rows = $rows->getResult($this->tempReturnType);
1✔
548

549
            $offset += $size;
1✔
550

551
            if ($rows === []) {
1✔
552
                continue;
1✔
553
            }
554

555
            foreach ($rows as $row) {
1✔
556
                if ($userFunc($row) === false) {
1✔
UNCOV
557
                    return;
×
558
                }
559
            }
560
        }
561
    }
562

563
    /**
564
     * Provides a shared instance of the Query Builder.
565
     *
566
     * @param non-empty-string|null $table
567
     *
568
     * @return BaseBuilder
569
     *
570
     * @throws ModelException
571
     */
572
    public function builder(?string $table = null)
573
    {
574
        // Check for an existing Builder
575
        if ($this->builder instanceof BaseBuilder) {
197✔
576
            // Make sure the requested table matches the builder
577
            if ((string) $table !== '' && $this->builder->getTable() !== $table) {
116✔
578
                return $this->db->table($table);
1✔
579
            }
580

581
            return $this->builder;
116✔
582
        }
583

584
        // We're going to force a primary key to exist
585
        // so we don't have overly convoluted code,
586
        // and future features are likely to require them.
587
        if ($this->primaryKey === '') {
197✔
588
            throw ModelException::forNoPrimaryKey(static::class);
1✔
589
        }
590

591
        $table = ((string) $table === '') ? $this->table : $table;
196✔
592

593
        // Ensure we have a good db connection
594
        if (! $this->db instanceof BaseConnection) {
196✔
UNCOV
595
            $this->db = Database::connect($this->DBGroup);
×
596
        }
597

598
        $builder = $this->db->table($table);
196✔
599

600
        // Only consider it "shared" if the table is correct
601
        if ($table === $this->table) {
196✔
602
            $this->builder = $builder;
196✔
603
        }
604

605
        return $builder;
196✔
606
    }
607

608
    /**
609
     * Captures the builder's set() method so that we can validate the
610
     * data here. This allows it to be used with any of the other
611
     * builder methods and still get validated data, like replace.
612
     *
613
     * @param object|row_array|string           $key    Field name, or an array of field/value pairs, or an object
614
     * @param bool|float|int|object|string|null $value  Field value, if $key is a single field
615
     * @param bool|null                         $escape Whether to escape values
616
     *
617
     * @return $this
618
     */
619
    public function set($key, $value = '', ?bool $escape = null)
620
    {
621
        if (is_object($key)) {
10✔
622
            $key = $key instanceof stdClass ? (array) $key : $this->objectToArray($key);
2✔
623
        }
624

625
        $data = is_array($key) ? $key : [$key => $value];
10✔
626

627
        foreach (array_keys($data) as $k) {
10✔
628
            $this->tempData['escape'][$k] = $escape;
10✔
629
        }
630

631
        $this->tempData['data'] = array_merge($this->tempData['data'] ?? [], $data);
10✔
632

633
        return $this;
10✔
634
    }
635

636
    protected function shouldUpdate($row): bool
637
    {
638
        if (parent::shouldUpdate($row) === false) {
27✔
639
            return false;
12✔
640
        }
641

642
        if ($this->useAutoIncrement === true) {
17✔
643
            return true;
14✔
644
        }
645

646
        // When useAutoIncrement feature is disabled, check
647
        // in the database if given record already exists
648
        return $this->where($this->primaryKey, $this->getIdValue($row))->countAllResults() === 1;
3✔
649
    }
650

651
    public function insert($row = null, bool $returnID = true)
652
    {
653
        if (isset($this->tempData['data'])) {
119✔
654
            if ($row === null) {
2✔
655
                $row = $this->tempData['data'];
1✔
656
            } else {
657
                $row = $this->transformDataToArray($row, 'insert');
1✔
658
                $row = array_merge($this->tempData['data'], $row);
1✔
659
            }
660
        }
661

662
        $this->escape   = $this->tempData['escape'] ?? [];
119✔
663
        $this->tempData = [];
119✔
664

665
        return parent::insert($row, $returnID);
119✔
666
    }
667

668
    protected function doProtectFieldsForInsert(array $row): array
669
    {
670
        if (! $this->protectFields) {
121✔
671
            return $row;
9✔
672
        }
673

674
        if ($this->allowedFields === []) {
112✔
675
            throw DataException::forInvalidAllowedFields(static::class);
1✔
676
        }
677

678
        foreach (array_keys($row) as $key) {
111✔
679
            // Do not remove the non-auto-incrementing primary key data.
680
            if ($this->useAutoIncrement === false && $key === $this->primaryKey) {
110✔
681
                continue;
25✔
682
            }
683

684
            if (! in_array($key, $this->allowedFields, true)) {
110✔
685
                unset($row[$key]);
25✔
686
            }
687
        }
688

689
        return $row;
111✔
690
    }
691

692
    public function update($id = null, $row = null): bool
693
    {
694
        if (isset($this->tempData['data'])) {
58✔
695
            if ($row === null) {
6✔
696
                $row = $this->tempData['data'];
5✔
697
            } else {
698
                $row = $this->transformDataToArray($row, 'update');
1✔
699
                $row = array_merge($this->tempData['data'], $row);
1✔
700
            }
701
        }
702

703
        $this->escape   = $this->tempData['escape'] ?? [];
58✔
704
        $this->tempData = [];
58✔
705

706
        return parent::update($id, $row);
58✔
707
    }
708

709
    protected function objectToRawArray($object, bool $onlyChanged = true, bool $recursive = false): array
710
    {
711
        return parent::objectToRawArray($object, $onlyChanged);
24✔
712
    }
713

714
    /**
715
     * Provides/instantiates the builder/db connection and model's table/primary key names and return type.
716
     *
717
     * @return array<int|string, mixed>|BaseBuilder|bool|float|int|object|string|null
718
     */
719
    public function __get(string $name)
720
    {
721
        if (parent::__isset($name)) {
50✔
722
            return parent::__get($name);
50✔
723
        }
724

725
        return $this->builder()->{$name} ?? null;
1✔
726
    }
727

728
    /**
729
     * Checks for the existence of properties across this model, builder, and db connection.
730
     */
731
    public function __isset(string $name): bool
732
    {
733
        if (parent::__isset($name)) {
47✔
734
            return true;
47✔
735
        }
736

737
        return isset($this->builder()->{$name});
1✔
738
    }
739

740
    /**
741
     * Provides direct access to method in the builder (if available)
742
     * and the database connection.
743
     *
744
     * @return $this|array<int|string, mixed>|BaseBuilder|bool|float|int|object|string|null
745
     */
746
    public function __call(string $name, array $params)
747
    {
748
        $builder = $this->builder();
48✔
749
        $result  = null;
48✔
750

751
        if (method_exists($this->db, $name)) {
48✔
752
            $result = $this->db->{$name}(...$params);
2✔
753
        } elseif (method_exists($builder, $name)) {
47✔
754
            $this->checkBuilderMethod($name);
46✔
755

756
            $result = $builder->{$name}(...$params);
44✔
757
        } else {
758
            throw new BadMethodCallException('Call to undefined method ' . static::class . '::' . $name);
1✔
759
        }
760

761
        if ($result instanceof BaseBuilder) {
45✔
762
            return $this;
44✔
763
        }
764

765
        return $result;
2✔
766
    }
767

768
    /**
769
     * Checks the Builder method name that should not be used in the Model.
770
     */
771
    private function checkBuilderMethod(string $name): void
772
    {
773
        if (in_array($name, $this->builderMethodsNotAvailable, true)) {
46✔
774
            throw ModelException::forMethodNotAvailable(static::class, $name . '()');
2✔
775
        }
776
    }
777
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc