Skip to content

feat: add closure-based Query Builder join conditions#10186

Open
memleakd wants to merge 2 commits intocodeigniter4:4.8from
memleakd:feat/join-condition-ergonomics
Open

feat: add closure-based Query Builder join conditions#10186
memleakd wants to merge 2 commits intocodeigniter4:4.8from
memleakd:feat/join-condition-ergonomics

Conversation

@memleakd
Copy link
Copy Markdown
Contributor

Description

This PR proposes a closure-based way to build Query Builder JOIN ON clauses.

Today, simple joins are easy:

$builder->join('orders', 'orders.user_id = users.id');

But once a join needs bound values, OR conditions, or grouped ON (...) logic, users often have to fall back to longer raw SQL strings or RawSql. That works, but it gives up some of the safety and readability the Query Builder usually provides.

This PR keeps the existing join() API intact and adds a native option for those more complex join conditions:

use CodeIgniter\Database\JoinClause;

$builder->join('orders', static function (JoinClause $join): void {
    $join->on('orders.user_id', 'users.id')
        ->where('orders.status', 'paid');
});

For grouped conditions:

$builder->join('orders', static function (JoinClause $join): void {
    $join->on('orders.user_id', 'users.id')
        ->groupStart()
            ->where('orders.status', 'paid')
            ->orWhere('orders.status', 'pending')
        ->groupEnd();
});

This produces a protected, bound JOIN ON clause without requiring users to manually write the whole condition as raw SQL.

The closure receives a JoinClause object with familiar Query Builder-style methods:

  • on() / orOn() for column comparisons
  • where() / orWhere() for bound value comparisons
  • groupStart() / orGroupStart() / notGroupStart() / orNotGroupStart() / groupEnd() for grouped conditions

Existing string and RawSql join conditions continue to work as before. RawSql remains the explicit escape hatch for cases that do not fit this small join-condition API.

Why

This helps real-world applications express safer and clearer joins, especially when joins include conditional matching, status filters, soft-delete checks, tenant constraints, or grouped OR logic inside the ON clause.

Instead of choosing between a simple protected join string and a fully raw condition, users get a CodeIgniter-native middle path that keeps identifiers protected and values bound.

Changes

  • Added CodeIgniter\Database\JoinClause.
  • Added closure support to BaseBuilder::join().
  • Reused the shared join condition compiler from the SQLSRV builder.
  • Updated Model PHPDoc for forwarded join() calls.
  • Added user guide docs and changelog entry.
  • Added focused tests for escaping, binds, grouped/nested conditions, invalid group state, aliases, and SQLSRV table naming.

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

- Add JoinClause for protected JOIN ON column and value conditions
- Support grouped and nested join conditions with Query Builder-style methods
- Share join condition compilation with SQLSRV
- Document the closure join API and add focused builder coverage

Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
@github-actions github-actions Bot added the 4.8 PRs that target the `4.8` branch. label May 10, 2026
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.8 PRs that target the `4.8` branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant