Skip to content
This repository was archived by the owner on Nov 21, 2023. It is now read-only.

Commit 5ef4679

Browse files
authored
Feat: Accept Builder argument (#3)
* Feat: Accept Builder argument * Doc: Update README
1 parent dd9c2f9 commit 5ef4679

File tree

4 files changed

+102
-2
lines changed

4 files changed

+102
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ Builder::hasByNonDependentSubquery(
131131
| Feature | [`mpyw/eloquent-has-by-join`](https://github.com/mpyw/eloquent-has-by-join) | `mpyw/eloquent-has-by-non-dependent-subquery` |
132132
|:----|:---:|:---:|
133133
| Minimum Laravel version | 5.6 | 5.8 |
134-
| Argument of optional constraints | `Illuminate\Database\Eloquent\Builder` | `Illuminate\Database\Eloquent\Relations\*` |
134+
| Argument of optional constraints | `Illuminate\Database\Eloquent\Builder` | `Illuminate\Database\Eloquent\Relations\*`<br>(`Builder` can be also accepted by specifying argument type) |
135135
| [Compoships](https://github.com/topclaudy/compoships) support |||
136136
| No subqueries || ❌<br>(Performance depends on database optimizers) |
137137
| No table collisions | ❌<br>(Sometimes you need to give aliases) ||

src/HasByNonDependentSubqueryMacro.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ protected function apply($relationMethod, string $whereInMethod, ?callable ...$c
8989
function (Relation $query) use ($relationMethods, $whereInMethod, $constraints) {
9090
// Apply optional constraints
9191
if ($currentConstraints = \array_shift($constraints)) {
92-
$currentConstraints($query);
92+
$currentConstraints($this->adjustArgumentTypeOfOptionalConstraints($currentConstraints, $query));
9393
}
9494
// Apply relations nested under
9595
if ($relationMethods) {
@@ -133,4 +133,23 @@ protected function applyForCurrentRelation(string $relationMethod, string $where
133133
}
134134
$this->query->{$whereInMethod}($keys->getQualifiedSourceKeyName(), $relation->getQuery());
135135
}
136+
137+
/**
138+
* From v1.1:
139+
* Relation will be automatically converted to Builder to prevent common mistakes on demand.
140+
*
141+
* @param callable $constraint
142+
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
143+
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation
144+
*/
145+
protected function adjustArgumentTypeOfOptionalConstraints(callable $constraint, Relation $relation)
146+
{
147+
$reflection = ReflectionCallable::from($constraint);
148+
149+
return $reflection->getNumberOfParameters() > 0
150+
&& ($parameter = $reflection->getParameters()[0])->hasType()
151+
&& \is_a($parameter->getType()->getName(), Builder::class, true)
152+
? $relation->getQuery()
153+
: $relation;
154+
}
136155
}

src/ReflectionCallable.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Mpyw\EloquentHasByNonDependentSubquery;
4+
5+
use Closure;
6+
use ReflectionFunction;
7+
use ReflectionMethod;
8+
9+
/**
10+
* Class ReflectionCallable
11+
*/
12+
class ReflectionCallable
13+
{
14+
/** @noinspection PhpDocMissingThrowsInspection */
15+
16+
/**
17+
* @param callable $callable
18+
* @return \ReflectionFunction|\ReflectionMethod
19+
*/
20+
public static function from(callable $callable)
21+
{
22+
if (\is_string($callable) && \strpos($callable, '::')) {
23+
$callable = \explode('::', $callable);
24+
} elseif (!$callable instanceof Closure && \is_object($callable)) {
25+
$callable = [$callable, '__invoke'];
26+
}
27+
28+
/* @noinspection PhpUnhandledExceptionInspection */
29+
return $callable instanceof Closure || \is_string($callable)
30+
? new ReflectionFunction($callable)
31+
: new ReflectionMethod($callable[0], $callable[1]);
32+
}
33+
}

tests/Test.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,25 @@
22

33
namespace Mpyw\EloquentHasByNonDependentSubquery\Tests;
44

5+
use DateTime;
56
use DomainException;
7+
use Illuminate\Database\Eloquent\Builder;
68
use Illuminate\Database\Eloquent\Relations\BelongsTo;
79
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
810
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
911
use Illuminate\Database\Eloquent\Relations\Relation;
1012
use Illuminate\Support\Str;
1113
use Mpyw\EloquentHasByNonDependentSubquery\EloquentHasByNonDependentSubqueryServiceProvider;
14+
use Mpyw\EloquentHasByNonDependentSubquery\ReflectionCallable;
1215
use Mpyw\EloquentHasByNonDependentSubquery\Tests\Models\Category;
1316
use Mpyw\EloquentHasByNonDependentSubquery\Tests\Models\Comment;
1417
use Mpyw\EloquentHasByNonDependentSubquery\Tests\Models\Post;
1518
use Mpyw\EloquentHasByNonDependentSubquery\Tests\Models\Tag;
1619
use Mpyw\EloquentHasByNonDependentSubquery\Tests\Models\User;
1720
use NilPortugues\Sql\QueryFormatter\Formatter;
1821
use Orchestra\Testbench\TestCase as BaseTestCase;
22+
use ReflectionFunction;
23+
use ReflectionMethod;
1924

2025
class Test extends BaseTestCase
2126
{
@@ -691,6 +696,34 @@ public function testOrDoesntHave(): void
691696
);
692697
}
693698

699+
public function testAcceptBuilderArgument(): void
700+
{
701+
$this->assertQueryEquals(
702+
<<<'EOD'
703+
select
704+
*
705+
from
706+
"comments"
707+
where
708+
"comments"."post_id" in (
709+
select
710+
"posts"."id"
711+
from
712+
"posts"
713+
where
714+
"posts"."deleted_at" is not null
715+
)
716+
EOD
717+
,
718+
Comment::query()->hasByNonDependentSubquery(
719+
'post',
720+
function (Builder $query) {
721+
$query->onlyTrashed();
722+
}
723+
)->withTrashed()
724+
);
725+
}
726+
694727
public function testMultiColumnRelation(): void
695728
{
696729
$this->expectException(DomainException::class);
@@ -714,4 +747,19 @@ public function testMorphToRelation(): void
714747

715748
Comment::query()->hasByNonDependentSubquery('commentable');
716749
}
750+
751+
public function testReflectionCallable(): void
752+
{
753+
$this->assertInstanceOf(ReflectionFunction::class, ReflectionCallable::from('strpos'));
754+
$this->assertInstanceOf(ReflectionFunction::class, ReflectionCallable::from(function () {}));
755+
756+
$this->assertInstanceOf(ReflectionMethod::class, ReflectionCallable::from('DateTime::createFromFormat'));
757+
$this->assertInstanceOf(ReflectionMethod::class, ReflectionCallable::from([DateTime::class, 'createFromFormat']));
758+
$this->assertInstanceOf(ReflectionMethod::class, ReflectionCallable::from([new DateTime(), 'format']));
759+
$this->assertInstanceOf(ReflectionMethod::class, ReflectionCallable::from(new class() {
760+
public function __invoke()
761+
{
762+
}
763+
}));
764+
}
717765
}

0 commit comments

Comments
 (0)