Skip to content

Commit bd02454

Browse files
committed
feat(model): add existsById helper
- add Model::existsById() for primary-key existence checks - document soft-delete and query-builder composition behavior - cover existing, missing, soft-deleted, constrained, and callback cases Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
1 parent 2ef1571 commit bd02454

5 files changed

Lines changed: 71 additions & 0 deletions

File tree

system/Model.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,14 @@ public function exists(bool $reset = true, bool $test = false)
558558
return $this->builder()->testMode($test)->exists($reset);
559559
}
560560

561+
/**
562+
* Determines whether the given primary key value exists.
563+
*/
564+
public function existsById(int|string $id): bool
565+
{
566+
return $this->where($this->table . '.' . $this->primaryKey, $id)->exists();
567+
}
568+
561569
/**
562570
* Determines whether the current Model query would not return any rows.
563571
*

tests/system/Models/ExistsModelTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace CodeIgniter\Models;
1515

1616
use PHPUnit\Framework\Attributes\Group;
17+
use Tests\Support\Models\EventModel;
1718
use Tests\Support\Models\UserModel;
1819

1920
/**
@@ -22,6 +23,46 @@
2223
#[Group('DatabaseLive')]
2324
final class ExistsModelTest extends LiveModelTestCase
2425
{
26+
public function testExistsByIdReturnsTrueForExistingPrimaryKey(): void
27+
{
28+
$this->createModel(UserModel::class);
29+
30+
$this->assertTrue($this->model->existsById(1));
31+
}
32+
33+
public function testExistsByIdReturnsFalseForMissingPrimaryKey(): void
34+
{
35+
$this->createModel(UserModel::class);
36+
37+
$this->assertFalse($this->model->existsById(999));
38+
}
39+
40+
public function testExistsByIdRespectsSoftDeletes(): void
41+
{
42+
$this->createModel(UserModel::class);
43+
$this->model->delete(1);
44+
45+
$this->assertFalse($this->model->existsById(1));
46+
$this->assertTrue($this->model->withDeleted()->existsById(1));
47+
}
48+
49+
public function testExistsByIdWorksWithCurrentModelQuery(): void
50+
{
51+
$this->createModel(UserModel::class);
52+
53+
$this->assertTrue($this->model->where('country', 'US')->existsById(1));
54+
$this->assertFalse($this->model->where('country', 'UK')->existsById(1));
55+
}
56+
57+
public function testExistsByIdDoesNotTriggerFindCallbacks(): void
58+
{
59+
$model = $this->createModel(EventModel::class);
60+
61+
$this->assertTrue($model->existsById(1));
62+
$this->assertFalse($model->hasToken('beforeFind'));
63+
$this->assertFalse($model->hasToken('afterFind'));
64+
}
65+
2566
public function testExistsRespectsSoftDeletes(): void
2667
{
2768
$this->createModel(UserModel::class);

user_guide_src/source/changelogs/v4.8.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ Model
247247
=====
248248

249249
- Added new ``chunkRows()`` method to ``CodeIgniter\Model`` for processing large datasets in smaller chunks.
250+
- Added new ``existsById()`` method to ``CodeIgniter\Model`` to check whether a row exists for a primary key without fetching it. See :ref:`model-exists-by-id`.
250251
- Added new ``firstOrInsert()`` method to ``CodeIgniter\Model`` that finds the first row matching the given attributes or inserts a new one. See :ref:`model-first-or-insert`.
251252

252253
Libraries

user_guide_src/source/models/model.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,23 @@ of just one:
509509
.. note:: If ``find()`` is called without parameters or with ``null``, it will return all rows in
510510
that model's table, effectively acting like ``findAll()``, though less explicit.
511511

512+
.. _model-exists-by-id:
513+
514+
existsById()
515+
------------
516+
517+
.. versionadded:: 4.8.0
518+
519+
Checks whether a row exists where the model's `$primaryKey`_ matches the
520+
given value, without fetching or hydrating the row:
521+
522+
.. literalinclude:: model/068.php
523+
524+
This method respects soft deletes. Use ``withDeleted()`` first if you need to
525+
check deleted rows. It can also be combined with Query Builder methods like
526+
``where()``. For condition-based existence checks that are not tied to a
527+
primary key, use ``exists()`` or ``doesntExist()``.
528+
512529
findColumn()
513530
------------
514531

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
$userExists = $userModel->existsById($userId);
4+
$activeUserExists = $userModel->where('active', 1)->existsById($userId);

0 commit comments

Comments
 (0)