Skip to content

Commit

Permalink
184 support wheredoesnthaverelation methods (#187)
Browse files Browse the repository at this point in the history
* qa: confirmed proper functioning of several eloquent relationship query methods

* fix: table alias was set to null in returndocs

---------

Co-authored-by: Deploy <[email protected]>
  • Loading branch information
LaravelFreelancerNL and Deploy authored Jan 19, 2025
1 parent d849565 commit c6ac1e6
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 31 deletions.
61 changes: 33 additions & 28 deletions docs/compatibility-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ update with join
delete / truncate

### Debugging
dd / dump / toSql / ddRawSql / dumpRawSql / toRawSql
dd / dump / toSql / ddRawSql? / dumpRawSql / toRawSql?

## <a name="eloquent"></a>Eloquent
The methods listed below are **specific** to Eloquent.
Expand All @@ -120,17 +120,17 @@ in the chapter above._

### Model CRUD
all / first / firstWhere / firstOr / firstOrFail /
firstOrCreate / firstOrNew? /
find / findOr / fresh? / refresh? /
create / createOrFirst / fill / save / update / updateOrCreate /
upsert / replicate / delete / destroy / truncate / softDeletes /
trashed? / restore? / withTrashed? / forceDelete
isDirty? / isClean / wasChanged / getOriginal /
pruning / query scopes? / saveQuietly / deleteQuietly /
forceDeleteQuietly / restoreQuietly
firstOrCreate / firstOrNew /
find / findOr? / fresh / refresh /
create / createOrFirst / fill? / save / update / updateOrCreate /
upsert / replicate? / delete / destroy / truncate / softDeletes? /
trashed? / restore? / withTrashed? / forceDelete /
isDirty? / isClean? / wasChanged? / getOriginal? /
pruning? / query scopes? / saveQuietly? / deleteQuietly? /
forceDeleteQuietly? / restoreQuietly?

#### Model comparison
is / isNot
is? / isNot?

### Relationships
- One To One
Expand All @@ -142,30 +142,35 @@ is / isNot

belongsTo / belongsToMany /
morphOne / morphTo / morphMany / morphMany / morphedByMany /
ofMany / latestOfMany / oldestOfMany
hasOne / hasMany / hasX / hasOneThrough / hasManyThrough /
throughX /
whereBelongsTo /
as /
withTimestamps /
ofMany? / latestOfMany? / oldestOfMany? /
has / hasOne / hasMany / hasOneThrough? / hasManyThrough? /
through? / whereBelongsTo? /

#### Pivot functions
withPivot /
wherePivot / wherePivotIn /wherePivotNotIn /
wherePivotBetween / wherePivotNotBetween /
wherePivotNull / wherePivotNotNull / orderByPivot /
using
as? / withPivot? /
wherePivot? / wherePivotIn? /wherePivotNotIn? /
wherePivotBetween? / wherePivotNotBetween? /
wherePivotNull? / wherePivotNotNull? / orderByPivot? /
using? / withTimestamps?

enforceMorphMap / getMorphClass / getMorphedModel / resolveRelationUsing
enforceMorphMap? / getMorphClass? / getMorphedModel? / resolveRelationUsing?

#### Query relationships
has / orHas / whereHas / whereRelation / doesntHave /
whereDoesntHave / whereHasMorph / whereDoesntHaveMorph
#### Querying Relationship Existence
has / orHas / whereHas / orWhereHas / whereRelation / orWhereRelation /
whereMorphRelation / orWhereMorphRelation

#### Querying Relationship Absence
doesntHave / orDoesntHave /
whereDoesntHave / orWhereDoesntHave / whereDoesntHaveRelation / orWhereDoesntHaveRelation /
whereMorphDoesntHaveRelation / orWhereMorphDoesntHaveRelation

#### Querying Morph To Relationships
whereHasMorph / orWhereHasMorph / whereDoesntHaveMorph / orWhereDoesntHaveMorph / whereMorphedTo? / whereNotMorphedTo?

#### Aggregating related models
withCount / loadCount /
withSum / loadSum / withExists / morphWithCount /loadMorphCount /
loadMorphCount
withCount / loadCount? /
withSum? / loadSum? / withExists / morphWithCount? /loadMorphCount? /
loadMorphCount?

#### Eager loading
with / without / withOnly / constrain /
Expand Down
4 changes: 4 additions & 0 deletions src/Query/Concerns/HandlesAliases.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public function getTableAlias(string|Expression $table): float|int|null|string
$table = 'Expression' . spl_object_id($table);
}

if ($this->isTableAlias($table)) {
return $table;
}

if (!isset($this->tableAliases[$table])) {
return null;
}
Expand Down
130 changes: 130 additions & 0 deletions tests/Eloquent/MorphToTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use Illuminate\Database\Eloquent\Builder;
use LaravelFreelancerNL\Aranguent\Testing\DatabaseTransactions;
use TestSetup\Models\Character;
use TestSetup\Models\Location;
Expand Down Expand Up @@ -35,3 +36,132 @@
expect($location->capturable)->toBeInstanceOf(Character::class);
expect($location->capturable->id)->toEqual('TheonGreyjoy');
});

test('whereHasMorph', function () {
$locations = Location::whereHasMorph(
'capturable',
Character::class,
function (Builder $query) {
$query->where('_key', 'TheonGreyjoy');
},
)->get();

expect(count($locations))->toEqual(1);
});

test('orWhereHasMorph', function () {
$locations = Location::where(function (Builder $query) {
$query->whereHasMorph(
'capturable',
Character::class,
function (Builder $query) {
$query->where('id', 'TheonGreyjoy');
},
)
->orWhereHasMorph(
'capturable',
Character::class,
function (Builder $query) {
$query->where('id', 'DaenerysTargaryen');
},
);
})->get();

expect(count($locations))->toEqual(6);
});

test('whereMorphRelation ', function () {
$locations = Location::whereMorphRelation(
'capturable',
Character::class,
'_key',
'TheonGreyjoy',
)
->get();

expect(count($locations))->toEqual(1);
});

test('orWhereMorphRelation', function () {
$locations = Location::where(function (Builder $query) {
$query->whereMorphRelation(
'capturable',
Character::class,
'id',
'TheonGreyjoy',
)
->orWhereMorphRelation(
'capturable',
Character::class,
'id',
'DaenerysTargaryen',
);
})->get();

expect($locations->count())->toEqual(6);
});

test('whereDoesntHaveMorph', function () {
$locations = Location::whereDoesntHaveMorph(
'capturable',
Character::class,
function (Builder $query) {
$query->where('id', 'DaenerysTargaryen');
},
)->get();

expect(count($locations))->toEqual(1);
});

test('orWhereDoesntHaveMorph', function () {
$locations = Location::where(function (Builder $query) {
$query->whereHasMorph(
'capturable',
Character::class,
function (Builder $query) {
$query->where('alive', true);
},
)
->orWhereDoesntHaveMorph(
'capturable',
Character::class,
function (Builder $query) {
$query->where('age', '<', 20);
},
);
})->get();

expect(count($locations))->toEqual(6);
});


test('whereMorphDoesntHaveRelation', function () {
$locations = Location::whereMorphDoesntHaveRelation(
'capturable',
Character::class,
'id',
'TheonGreyjoy',
)->get();

expect(count($locations))->toEqual(5);
});

test('orWhereMorphDoesntHaveRelation', function () {
$locations = Location::where(function (Builder $query) {
$query->whereMorphDoesntHaveRelation(
'capturable',
Character::class,
'id',
'DaenerysTargaryen',
)
->orWhereMorphDoesntHaveRelation(
'capturable',
Character::class,
'age',
'<',
20,
);
})->get();

expect(count($locations))->toEqual(1);
});
134 changes: 131 additions & 3 deletions tests/Eloquent/RelationshipQueriesTest.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<?php

use Illuminate\Database\Eloquent\Builder;
use LaravelFreelancerNL\Aranguent\Testing\DatabaseTransactions;
use TestSetup\Models\Character;
use TestSetup\Models\House;
use TestSetup\Models\Location;

uses(
DatabaseTransactions::class,
Expand All @@ -17,15 +20,140 @@
expect(count($characters))->toEqual(1);
});

test('has morph', function () {
$characters = Character::has('tags')->get();

expect(count($characters))->toEqual(2);
});


test('orHas', function () {
$characters = Character::has('leads')
->orHas('captured')
->get();

expect(count($characters))->toEqual(4);
});

test('doesntHave', function () {
$characters = Character::doesntHave('leads')->get();

expect(count($characters))->toEqual(40);
});

test('has on morphed relation', function () {
$characters = Character::has('tags')->get();
test('orDoesntHave', function () {
$characters = Character::where(function (Builder $query) {
$query->doesntHave('leads')
->orDoesntHave('conquered');
})
->get();

expect(count($characters))->toEqual(2);
$daenarys = $characters->first(function (Character $character) {
return $character->id === 'DaenerysTargaryen';
});

expect(count($characters))->toEqual(42);
expect($daenarys)->toBeNull();
});

test('whereHas', function () {
$locations = Location::whereHas('leader', function (Builder $query) {
$query->where('age', '<', 30);
})
->distinct()
->pluck('led_by');

expect($locations->count())->toBe(2);
expect($locations[0])->toBe('DaenerysTargaryen');
expect($locations[1])->toBe('SansaStark');
});

test('orWhereHas', function () {
$locations = Location::where(function (Builder $query) {
$query->whereHas('leader', function (Builder $query) {
$query->where('age', '<', 15);
})->orWhereHas('leader', function (Builder $query) {
$query->where('age', '>', 30);
});
})
->distinct()
->pluck('led_by');

expect($locations->count())->toBe(2);
expect($locations[0])->toBe('CerseiLannister');
expect($locations[1])->toBe('SansaStark');
});

test('whereDoesntHave', function () {
$characters = Character::whereDoesntHave('leads', function (Builder $query) {
$query->where('name', 'Astapor');
})->get();

$daenarys = $characters->first(function (Character $character) {
return $character->id === 'DaenerysTargaryen';
});
expect($characters->count())->toBe(42);
expect($daenarys)->toBeNull();
});

test('orWhereDoesntHave', function () {
$houses = House::where(function (Builder $query) {
$query->whereDoesntHave('head', function (Builder $query) {
$query->where('age', '<', 20);
})
->orWhereDoesntHave('head', function (Builder $query) {
$query->whereNull('age');
});
})->get();

expect($houses[0]->name)->toBe('Stark');
expect($houses[1]->name)->toBe('Targaryen');
});



test('whereRelation', function () {
$locations = Location::whereRelation('leader', 'age', '<', 30)
->distinct()
->pluck('led_by');

expect($locations->count())->toBe(2);
expect($locations[0])->toBe('DaenerysTargaryen');
expect($locations[1])->toBe('SansaStark');
});

test('orWhereRelation', function () {
$locations = Location::where(function (Builder $query) {
$query->whereRelation('leader', 'age', '<', 15)
->orWhereRelation('leader', 'age', '>', 30);
})
->distinct()
->pluck('led_by');

expect($locations->count())->toBe(2);
expect($locations[0])->toBe('CerseiLannister');
expect($locations[1])->toBe('SansaStark');
});

test('whereDoesntHaveRelation', function () {
$characters = Character::whereDoesntHaveRelation('leads', 'name', 'Astapor')->get();

$daenarys = $characters->first(function (Character $character) {
return $character->id === 'DaenerysTargaryen';
});

expect($characters->count())->toBe(42);
expect($daenarys)->toBeNull();
});

test('orWhereDoesntHaveRelation', function () {
$houses = House::where(function (Builder $query) {
$query->whereDoesntHaveRelation('head', 'age', '<', 20)
->orWhereDoesntHaveRelation('head', 'age', null);
})->get();

expect($houses[0]->name)->toBe('Stark');
expect($houses[1]->name)->toBe('Targaryen');
});

test('withCount', function () {
Expand Down

0 comments on commit c6ac1e6

Please sign in to comment.