Skip to content

Commit

Permalink
feat: create HasBuilder trait
Browse files Browse the repository at this point in the history
  • Loading branch information
calebdw committed Jun 21, 2024
1 parent 7786955 commit 76a3a0d
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 41 deletions.
124 changes: 124 additions & 0 deletions src/Illuminate/Database/Eloquent/HasBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace Illuminate\Database\Eloquent;

/**
* @template TBuilder of \Illuminate\Database\Eloquent\Builder
*/
trait HasBuilder
{
/**
* Begin querying the model.
*
* @return TBuilder
*/
public static function query()
{
return parent::query();
}

/**
* Create a new Eloquent query builder for the model.
*
* @param \Illuminate\Database\Query\Builder $query
* @return TBuilder
*/
public function newEloquentBuilder($query)
{
return parent::newEloquentBuilder($query);
}

/**
* Get a new query builder for the model's table.
*
* @return TBuilder
*/
public function newQuery()
{
return parent::newQuery();
}

/**
* Get a new query builder that doesn't have any global scopes or eager loading.
*
* @return TBuilder
*/
public function newModelQuery()
{
return parent::newModelQuery();
}

/**
* Get a new query builder with no relationships loaded.
*
* @return TBuilder
*/
public function newQueryWithoutRelationships()
{
return parent::newQueryWithoutRelationships();
}

/**
* Get a new query builder that doesn't have any global scopes.
*
* @return TBuilder
*/
public function newQueryWithoutScopes()
{
return parent::newQueryWithoutScopes();
}

/**
* Get a new query instance without a given scope.
*
* @param \Illuminate\Database\Eloquent\Scope|string $scope
* @return TBuilder
*/
public function newQueryWithoutScope($scope)
{
return parent::newQueryWithoutScope($scope);
}

/**
* Get a new query to restore one or more models by their queueable IDs.
*
* @param array|int $ids
* @return TBuilder
*/
public function newQueryForRestoration($ids)
{
return parent::newQueryForRestoration($ids);
}

/**
* Begin querying the model on a given connection.
*
* @param string|null $connection
* @return TBuilder
*/
public static function on($connection = null)
{
return parent::on($connection);
}

/**
* Begin querying the model on the write connection.
*
* @return TBuilder
*/
public static function onWriteConnection()
{
return parent::onWriteConnection();
}

/**
* Begin querying a model with eager loading.
*
* @param array|string $relations
* @return TBuilder
*/
public static function with($relations)
{
return parent::with($relations);
}
}
9 changes: 8 additions & 1 deletion src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt
*/
protected static $isBroadcasting = true;

/**
* The Eloquent query builder class to use for the model.
*
* @var class-string<\Illuminate\Database\Eloquent\Builder<*>>
*/
protected static string $builder = Builder::class;

/**
* The name of the "created at" column.
*
Expand Down Expand Up @@ -1568,7 +1575,7 @@ public function newQueryForRestoration($ids)
*/
public function newEloquentBuilder($query)
{
return new Builder($query);
return new static::$builder($query);
}

/**
Expand Down
101 changes: 61 additions & 40 deletions types/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
namespace Illuminate\Types\Builder;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\HasBuilder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Query\Builder as QueryBuilder;
use User;

use function PHPStan\Testing\assertType;

/** @param \Illuminate\Database\Eloquent\Builder<\User> $query */
function test(Builder $query, Post $post, ChildPost $childPost, Comment $comment): void
{
function test(
Builder $query,
Post $post,
ChildPost $childPost,
Comment $comment,
QueryBuilder $queryBuilder
): void {
assertType('Illuminate\Database\Eloquent\Builder<User>', $query->where('id', 1));
assertType('Illuminate\Database\Eloquent\Builder<User>', $query->orWhere('name', 'John'));
assertType('Illuminate\Database\Eloquent\Builder<User>', $query->whereNot('status', 'active'));
Expand Down Expand Up @@ -103,22 +110,59 @@ function test(Builder $query, Post $post, ChildPost $childPost, Comment $comment
assertType('int', $page);
});

assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->query());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->query()->where('foo', 'bar'));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->query()->foo());
assertType('Illuminate\Types\Builder\Post', $post->query()->create(['name' => 'John']));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->query());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->query()->where('foo', 'bar'));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->query()->foo());
assertType('Illuminate\Types\Builder\ChildPost', $childPost->query()->create(['name' => 'John']));
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query());
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query()->where('foo', 'bar'));
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->query()->foo());
assertType('Illuminate\Types\Builder\Comment', $comment->query()->create(['name' => 'John']));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', Post::query());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', Post::on());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', Post::onWriteConnection());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', Post::with([]));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newQuery());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newEloquentBuilder($queryBuilder));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newModelQuery());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newQueryWithoutRelationships());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newQueryWithoutScopes());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newQueryWithoutScope('foo'));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newQueryForRestoration(1));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newQuery()->where('foo', 'bar'));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\Post>', $post->newQuery()->foo());
assertType('Illuminate\Types\Builder\Post', $post->newQuery()->create(['name' => 'John']));

assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', ChildPost::query());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', ChildPost::on());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', ChildPost::onWriteConnection());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', ChildPost::with([]));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newQuery());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newEloquentBuilder($queryBuilder));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newModelQuery());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newQueryWithoutRelationships());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newQueryWithoutScopes());
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newQueryWithoutScope('foo'));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newQueryForRestoration(1));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newQuery()->where('foo', 'bar'));
assertType('Illuminate\Types\Builder\CommonBuilder<Illuminate\Types\Builder\ChildPost>', $childPost->newQuery()->foo());
assertType('Illuminate\Types\Builder\ChildPost', $childPost->newQuery()->create(['name' => 'John']));

assertType('Illuminate\Types\Builder\CommentBuilder', Comment::query());
assertType('Illuminate\Types\Builder\CommentBuilder', Comment::on());
assertType('Illuminate\Types\Builder\CommentBuilder', Comment::onWriteConnection());
assertType('Illuminate\Types\Builder\CommentBuilder', Comment::with([]));
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQuery());
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newEloquentBuilder($queryBuilder));
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newModelQuery());
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQueryWithoutRelationships());
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQueryWithoutScopes());
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQueryWithoutScope('foo'));
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQueryForRestoration(1));
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQuery()->where('foo', 'bar'));
assertType('Illuminate\Types\Builder\CommentBuilder', $comment->newQuery()->foo());
assertType('Illuminate\Types\Builder\Comment', $comment->newQuery()->create(['name' => 'John']));
}

class Post extends Model
{
/** @use HasBuilder<CommonBuilder<static>> */
use HasBuilder;

protected static string $builder = CommonBuilder::class;

/** @return HasMany<User, $this> */
public function users(): HasMany
{
Expand All @@ -130,22 +174,6 @@ public function taggable(): MorphTo
{
return $this->morphTo();
}

/** @return CommonBuilder<static> */
public static function query(): CommonBuilder
{
/** @var CommonBuilder<static> */
return parent::query();
}

/**
* @param \Illuminate\Database\Query\Builder $query
* @return CommonBuilder<*>
*/
public function newEloquentBuilder($query): CommonBuilder
{
return new CommonBuilder($query);
}
}

class ChildPost extends Post
Expand All @@ -154,17 +182,10 @@ class ChildPost extends Post

class Comment extends Model
{
public static function query(): CommentBuilder
{
/** @var CommentBuilder */
return parent::query();
}
/** @use HasBuilder<CommentBuilder> */
use HasBuilder;

/** @param \Illuminate\Database\Query\Builder $query */
public function newEloquentBuilder($query): CommentBuilder
{
return new CommentBuilder($query);
}
protected static string $builder = CommentBuilder::class;
}

/**
Expand Down

0 comments on commit 76a3a0d

Please sign in to comment.