diff --git a/docs/compatibility-list.md b/docs/compatibility-list.md index e458dd4..cc86823 100644 --- a/docs/compatibility-list.md +++ b/docs/compatibility-list.md @@ -111,7 +111,7 @@ update with join delete / truncate ### Debugging -dd / dump / toSql / ddRawSql / dumpRawSql / toRawSql +dd / dump / toSql / ddRawSql? / dumpRawSql / toRawSql? ## Eloquent The methods listed below are **specific** to Eloquent. @@ -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 @@ -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 / diff --git a/src/Query/Concerns/HandlesAliases.php b/src/Query/Concerns/HandlesAliases.php index 6dcd710..784a57c 100644 --- a/src/Query/Concerns/HandlesAliases.php +++ b/src/Query/Concerns/HandlesAliases.php @@ -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; } diff --git a/tests/Eloquent/MorphToTest.php b/tests/Eloquent/MorphToTest.php index 51645cf..5674cca 100644 --- a/tests/Eloquent/MorphToTest.php +++ b/tests/Eloquent/MorphToTest.php @@ -1,5 +1,6 @@ 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); +}); diff --git a/tests/Eloquent/RelationshipQueriesTest.php b/tests/Eloquent/RelationshipQueriesTest.php index 0740782..5f43160 100644 --- a/tests/Eloquent/RelationshipQueriesTest.php +++ b/tests/Eloquent/RelationshipQueriesTest.php @@ -1,7 +1,10 @@ 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 () {