Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When/WhenNot methods for db in a trait #6574

Merged
merged 7 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions system/Database/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Closure;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\DataException;
use CodeIgniter\Traits\ConditionalTrait;
use InvalidArgumentException;

/**
Expand All @@ -25,6 +26,8 @@
*/
class BaseBuilder
{
use ConditionalTrait;

/**
* Reset DELETE data flag
*
Expand Down
61 changes: 61 additions & 0 deletions system/Traits/ConditionalTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Traits;

trait ConditionalTrait
{
/**
* Only runs the query when $condition evaluates to true
*
* @template TWhen of mixed
*
* @phpstan-param TWhen $condition
* @phpstan-param callable(self, TWhen): mixed $callback
* @phpstan-param (callable(self): mixed)|null $defaultCallback
* @param array|bool|float|int|object|resource|string|null $condition
MGatner marked this conversation as resolved.
Show resolved Hide resolved
MGatner marked this conversation as resolved.
Show resolved Hide resolved
*
* @return $this
*/
public function when($condition, callable $callback, ?callable $defaultCallback = null): self
kenjis marked this conversation as resolved.
Show resolved Hide resolved
{
if ($condition) {
$callback($this, $condition);
} elseif ($defaultCallback) {
$defaultCallback($this);
}

return $this;
}

/**
* Only runs the query when $condition evaluates to false
*
* @template TWhenNot of mixed
*
* @phpstan-param TWhenNot $condition
* @phpstan-param callable(self, TWhenNot): mixed $callback
* @phpstan-param (callable(self): mixed)|null $defaultCallback
* @param array|bool|float|int|object|resource|string|null $condition
lonnieezell marked this conversation as resolved.
Show resolved Hide resolved
*
* @return $this
*/
public function whenNot($condition, callable $callback, ?callable $defaultCallback = null): self
kenjis marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whenNot as the inverse of when seems quite not natural for me. It's a bit off. How about other antonyms of when:

  • except
  • unless
  • if not
  • lest

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method expects the condition to evaluate to false, which makes except and unless not make semantic sense. ifNot as an opposite of when feels wrong also. lest feels like the condition should evaluate to true to me, also.

whenNot does at least match the pattern of other methods like where/whereNot. I'm open to suggestions but I'm not sure any of these fit, unless we change the whole method to suit the name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except/unless makes sense to me if the statement is read in a specific way. In the context of the DB builder, since I know the usage of when then when I read its inverse (except/unless):

$builder = $this->db->table('users');
$user = model('Users')->find(1);

return $builder->except($user, fn ($query) { /* some code */ })->get();

In this example, I would read the code as "Except when $user is truthy (or a found user record), let the $builder execute the callable then get the DB result."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whenNot is easy to understand for non native English speakers.
It is logical. The when whenNot pair is easy to remember, because we already have where/whereNot.

except is difficult to imagine as the opposite of when.

{
if (! $condition) {
$callback($this, $condition);
} elseif ($defaultCallback) {
$defaultCallback($this);
}

return $this;
MGatner marked this conversation as resolved.
Show resolved Hide resolved
}
}
165 changes: 165 additions & 0 deletions tests/system/Database/Builder/WhenTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Database\Builder;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockConnection;

/**
* @internal
*/
final class WhenTest extends CIUnitTestCase
{
/**
* @var MockConnection
*/
protected $db;

protected function setUp(): void
{
parent::setUp();

$this->db = new MockConnection([]);
}

public function testWhenTrue()
{
$builder = $this->db->table('jobs');

$expectedSQL = 'SELECT * FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));

$builder = $builder->when(true, static function ($query) {
$query->select('id');
});

$expectedSQL = 'SELECT "id" FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenTruthy()
{
$builder = $this->db->table('jobs');

$builder = $builder->when('abc', static function ($query) {
$query->select('id');
});

$expectedSQL = 'SELECT "id" FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenRunsDefaultWhenFalse()
{
$builder = $this->db->table('jobs');

$builder = $builder->when(false, static function ($query) {
$query->select('id');
}, static function ($query) {
$query->select('name');
});

$expectedSQL = 'SELECT "name" FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenDoesntModifyWhenFalse()
{
$builder = $this->db->table('jobs');

$builder = $builder->when(false, static function ($query) {
$query->select('id');
});

$expectedSQL = 'SELECT * FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenPassesParemeters()
{
$builder = $this->db->table('jobs');
$name = 'developer';

$builder = $builder->when($name, static function ($query, $name) {
$query->where('name', $name);
});

$expectedSQL = 'SELECT * FROM "jobs" WHERE "name" = \'developer\'';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenNotFalse()
{
$builder = $this->db->table('jobs');

$expectedSQL = 'SELECT * FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));

$builder = $builder->whenNot(false, static function ($query) {
$query->select('id');
});

$expectedSQL = 'SELECT "id" FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenNotFalsey()
{
$builder = $this->db->table('jobs');

$builder = $builder->whenNot('0', static function ($query) {
$query->select('id');
});

$expectedSQL = 'SELECT "id" FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenNotRunsDefaultWhenTrue()
{
$builder = $this->db->table('jobs');

$builder = $builder->whenNot(true, static function ($query) {
$query->select('id');
}, static function ($query) {
$query->select('name');
});

$expectedSQL = 'SELECT "name" FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenNotDoesntModifyWhenFalse()
{
$builder = $this->db->table('jobs');

$builder = $builder->whenNot(true, static function ($query) {
$query->select('id');
});

$expectedSQL = 'SELECT * FROM "jobs"';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

public function testWhenNotPassesParemeters()
{
$builder = $this->db->table('jobs');
$name = '0';

$builder = $builder->whenNot($name, static function ($query, $name) {
$query->where('name', $name);
});

$expectedSQL = 'SELECT * FROM "jobs" WHERE "name" = \'0\'';
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}
}
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Database
- Improved the SQL structure for ``Builder::updateBatch()``. See :ref:`update-batch` for the details.
- Improved data returned by ``BaseConnection::getForeignKeyData()``. All DBMS returns the same structure.
- ``Forge::addForeignKey()`` now includes a name parameter to manual set foreign key names. Not supported in SQLite3.
- Added ``when()`` and ``whenNot()`` methods to conditionally add clauses to the query. See :ref:`BaseBuilder::when() <db-builder-when>` for details.

Model
=====
Expand Down
42 changes: 42 additions & 0 deletions user_guide_src/source/database/query_builder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,48 @@ that it produces a **DELETE** SQL string instead of an **INSERT** SQL string.

For more information view documentation for ``$builder->getCompiledInsert()``.

**********************
Conditional Statements
**********************

.. _db-builder-when:

When
====

$builder->when()
----------------

This allows modifying the query based on a condition without breaking out of the
query builder chain. The first parameter is the condition, and it should evaluate
to a boolean. The second parameter is a callable that will be ran
when the condition is true.

For example, you might only want to apply a given WHERE statement based on the
value sent within an HTTP request:

.. literalinclude:: query_builder/105.php

Since the condition is evaluated as ``true``, the callable will be called. The value
set in the condition will be passed as the second parameter to the callable so it
can be used in the query.

Sometimes you might want to apply a different statement if the condition evaluates to false.
This can be accomplished by providing a second closure:

.. literalinclude:: query_builder/106.php

WhenNot
=======

$builder->whenNot()
-------------------
lonnieezell marked this conversation as resolved.
Show resolved Hide resolved

This works exactly the same way as ``$builder->when()`` except that it will
only run the callable when the condition evaluates to ``false``, instead of ``true`` like ``when()``.

.. literalinclude:: query_builder/107.php

***************
Method Chaining
***************
Expand Down
9 changes: 9 additions & 0 deletions user_guide_src/source/database/query_builder/105.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

$status = service('request')->getPost('status');

$users = $this->db->table('users')
->when($status, static function ($query, $status) {
$query->where('status', $status);
})
->get();
11 changes: 11 additions & 0 deletions user_guide_src/source/database/query_builder/106.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

$onlyInactive = service('request')->getPost('return_inactive');

$users = $this->db->table('users')
->when($onlyInactive, static function ($query, $onlyInactive) {
$query->where('status', 'inactive');
}, static function ($query) {
$query->where('status', 'active');
})
->get();
9 changes: 9 additions & 0 deletions user_guide_src/source/database/query_builder/107.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

$status = service('request')->getPost('status');
lonnieezell marked this conversation as resolved.
Show resolved Hide resolved

$users = $this->db->table('users')
->whenNot($status, static function ($query, $status) {
$query->where('active', 0);
})
->get();