From 8f3a5308ce02a9b31e9112431da579fe126e7fe8 Mon Sep 17 00:00:00 2001 From: jim Date: Thu, 4 Dec 2025 19:39:24 +0000 Subject: [PATCH 1/3] Add PHPDoc annotations and improve type safety --- database/factories/PostFactory.php | 7 +- database/factories/PostSectionFactory.php | 3 + .../factories/SoftDeletingPostFactory.php | 4 +- database/factories/TagFactory.php | 3 + phpstan-baseline.neon | 126 ++++++++++++------ phpstan.neon.dist | 2 +- src/Concerns/HasDrafts.php | 50 +++++-- src/Concerns/Publishes.php | 6 +- src/Http/Middleware/WithDraftsMiddleware.php | 1 + src/Scopes/PublishingScope.php | 1 + tests/app/Models/Post.php | 17 +++ tests/app/Models/PostSection.php | 10 +- tests/app/Models/SoftDeletingPost.php | 3 + tests/app/Models/Tag.php | 10 +- 14 files changed, 183 insertions(+), 60 deletions(-) diff --git a/database/factories/PostFactory.php b/database/factories/PostFactory.php index 7a4082f..61e1691 100644 --- a/database/factories/PostFactory.php +++ b/database/factories/PostFactory.php @@ -4,6 +4,9 @@ use Oddvalue\LaravelDrafts\Tests\app\Models\Post; +/** + * @extends \Illuminate\Database\Eloquent\Factories\Factory + */ class PostFactory extends \Illuminate\Database\Eloquent\Factories\Factory { protected $model = Post::class; @@ -19,7 +22,7 @@ public function definition() ]; } - public function draft() + public function draft(): static { return $this->state(function () { return [ @@ -29,7 +32,7 @@ public function draft() }); } - public function published() + public function published(): static { return $this->state(function () { return [ diff --git a/database/factories/PostSectionFactory.php b/database/factories/PostSectionFactory.php index 80882f9..d903520 100644 --- a/database/factories/PostSectionFactory.php +++ b/database/factories/PostSectionFactory.php @@ -4,6 +4,9 @@ use Oddvalue\LaravelDrafts\Tests\app\Models\PostSection; +/** + * @extends \Illuminate\Database\Eloquent\Factories\Factory + */ class PostSectionFactory extends \Illuminate\Database\Eloquent\Factories\Factory { protected $model = PostSection::class; diff --git a/database/factories/SoftDeletingPostFactory.php b/database/factories/SoftDeletingPostFactory.php index 8d459e4..957b96c 100644 --- a/database/factories/SoftDeletingPostFactory.php +++ b/database/factories/SoftDeletingPostFactory.php @@ -18,7 +18,7 @@ public function definition() ]; } - public function draft() + public function draft(): static { return $this->state(function () { return [ @@ -28,7 +28,7 @@ public function draft() }); } - public function published() + public function published(): static { return $this->state(function () { return [ diff --git a/database/factories/TagFactory.php b/database/factories/TagFactory.php index 3570e34..7d484bf 100644 --- a/database/factories/TagFactory.php +++ b/database/factories/TagFactory.php @@ -4,6 +4,9 @@ use Oddvalue\LaravelDrafts\Tests\app\Models\Tag; +/** + * @extends \Illuminate\Database\Eloquent\Factories\Factory + */ class TagFactory extends \Illuminate\Database\Eloquent\Factories\Factory { protected $model = Tag::class; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d879162..05cffed 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -24,30 +24,6 @@ parameters: count: 3 path: src/Scopes/PublishingScope.php - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:getQualifiedIsPublishedColumn\(\)\.$#' - identifier: method.notFound - count: 1 - path: src/Scopes/PublishingScope.php - - - - message: '#^Access to an undefined property Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:\$drafts\.$#' - identifier: property.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Access to an undefined property Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:\$is_current\.$#' - identifier: property.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Access to an undefined property Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:\$is_published\.$#' - identifier: property.notFound - count: 1 - path: tests/app/Models/Post.php - - message: '#^Access to undefined constant static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\:\:IS_CURRENT\.$#' identifier: classConstant.notFound @@ -145,49 +121,115 @@ parameters: path: tests/app/Models/Post.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\:\:current\(\)\.$#' + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\\:\:onlyDrafts\(\)\.$#' identifier: method.notFound count: 2 path: tests/app/Models/Post.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\:\:onlyDrafts\(\)\.$#' + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\\:\:published\(\)\.$#' identifier: method.notFound - count: 1 + count: 2 path: tests/app/Models/Post.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\:\:published\(\)\.$#' + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\\:\:withDrafts\(\)\.$#' identifier: method.notFound - count: 2 + count: 3 path: tests/app/Models/Post.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\:\:withDrafts\(\)\.$#' - identifier: method.notFound - count: 2 + message: '#^Call to function property_exists\(\) with \$this\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\) and ''draftableRelations'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 path: tests/app/Models/Post.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\\:\:withDrafts\(\)\.$#' - identifier: method.notFound + message: '#^Using nullsafe method call on non\-nullable type mixed\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 path: tests/app/Models/Post.php - - message: '#^Call to function property_exists\(\) with \$this\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\) and ''draftableRelations'' will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType + message: '#^Method Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:getPublisherColumns\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: tests/app/Models/Post.php + path: src/Concerns/HasDrafts.php + + - + message: '#^Method Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:getDraftAttribute\(\) should return static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\|null but returns Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\|null\.$#' + identifier: return.type + count: 3 + path: src/Concerns/HasDrafts.php - - message: '#^Right side of && is always true\.$#' - identifier: booleanAnd.rightAlwaysTrue + message: '#^Class Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post uses generic trait Illuminate\\Database\\Eloquent\\Factories\\HasFactory but does not specify its types\: TFactory$#' + identifier: missingType.generics count: 1 path: tests/app/Models/Post.php - - message: '#^Using nullsafe method call on non\-nullable type mixed\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull + message: '#^Class Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\PostSection uses generic trait Illuminate\\Database\\Eloquent\\Factories\\HasFactory but does not specify its types\: TFactory$#' + identifier: missingType.generics count: 1 - path: tests/app/Models/Post.php + path: tests/app/Models/PostSection.php + + - + message: '#^Class Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Tag uses generic trait Illuminate\\Database\\Eloquent\\Factories\\HasFactory but does not specify its types\: TFactory$#' + identifier: missingType.generics + count: 1 + path: tests/app/Models/Tag.php + + - + message: '#^Method Oddvalue\\LaravelDrafts\\Database\\Factories\\PostFactory\:\:definition\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: database/factories/PostFactory.php + + - + message: '#^Method Oddvalue\\LaravelDrafts\\Database\\Factories\\PostSectionFactory\:\:definition\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: database/factories/PostSectionFactory.php + + - + message: '#^Method Oddvalue\\LaravelDrafts\\Database\\Factories\\SoftDeletingPostFactory\:\:definition\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: database/factories/SoftDeletingPostFactory.php + + - + message: '#^Method Oddvalue\\LaravelDrafts\\Database\\Factories\\TagFactory\:\:definition\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: database/factories/TagFactory.php + + - + message: '#^Cannot call method getDraftableAttributes\(\) on class\-string\|object\.$#' + identifier: method.nonObject + count: 2 + path: src/Concerns/HasDrafts.php + + - + message: '#^Parameter \#2 \$callback of static method Illuminate\\Database\\Eloquent\\Model\:\:registerModelEvent\(\) expects array\|\(callable\(\)\: mixed\)\|class\-string\|Illuminate\\Events\\QueuedClosure, Closure\|string given\.$#' + identifier: argument.type + count: 2 + path: src/Concerns/HasDrafts.php + + - + message: '#^Method Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:getQualifiedPublisherColumns\(\) should return array\{id\: string, type\: string\} but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Concerns/HasDrafts.php + + - + message: '#^Parameter \#2 \$callback of static method Illuminate\\Database\\Eloquent\\Model\:\:registerModelEvent\(\) expects array\|\(callable\(\)\: mixed\)\|class\-string\|Illuminate\\Events\\QueuedClosure, Closure\|string given\.$#' + identifier: argument.type + count: 2 + path: src/Concerns/Publishes.php + + - + message: '#^Cannot access offset ''Illuminate\\\\Contracts\\\\Http\\\\Kernel'' on Illuminate\\Contracts\\Foundation\\Application\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/LaravelDraftsServiceProvider.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 896f43b..aadfafb 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,7 +2,7 @@ includes: - phpstan-baseline.neon parameters: - level: 4 + level: 8 paths: - src - config diff --git a/src/Concerns/HasDrafts.php b/src/Concerns/HasDrafts.php index d09916f..7a75e4f 100644 --- a/src/Concerns/HasDrafts.php +++ b/src/Concerns/HasDrafts.php @@ -103,14 +103,14 @@ protected function newRevision(): void $revision = $updatingModel?->replicate(); static::saved(function (Model $model) use ($updatingModel, $revision): void { - if ($model->isNot($this)) { + if ($model->isNot($this) || $revision === null || $updatingModel === null) { return; } $revision->{$this->getCreatedAtColumn()} = $updatingModel->{$this->getCreatedAtColumn()}; $revision->{$this->getUpdatedAtColumn()} = $updatingModel->{$this->getUpdatedAtColumn()}; - $revision->is_current = false; - $revision->is_published = false; + $revision->{$this->getIsCurrentColumn()} = false; + $revision->{$this->getIsPublishedColumn()} = false; $revision->saveQuietly(['timestamps' => false]); // Preserve the existing updated_at @@ -142,6 +142,9 @@ public function generateUuid(): void $this->{$this->getUuidColumn()} = Str::uuid(); } + /** + * @return array + */ public function getDraftableAttributes(): array { return $this->getAttributes(); @@ -242,11 +245,17 @@ public function replicateAndAssociateDraftableRelations(Model $published): void }); } + /** + * @return array + */ public function getDraftableRelations(): array { return property_exists($this, 'draftableRelations') ? $this->draftableRelations : []; } + /** + * @param array $options + */ public function saveAsDraft(array $options = []): bool { if ($this->fireModelEvent('savingAsDraft') === false || $this->fireModelEvent('saving') === false) { @@ -308,6 +317,10 @@ public static function savedAsDraft(string|\Closure $callback): void static::registerModelEvent('drafted', $callback); } + /** + * @param array $attributes + * @param array $options + */ public function updateAsDraft(array $attributes = [], array $options = []): bool { if (! $this->exists) { @@ -317,6 +330,9 @@ public function updateAsDraft(array $attributes = [], array $options = []): bool return $this->fill($attributes)->saveAsDraft($options); } + /** + * @param array ...$attributes + */ public static function createDraft(...$attributes): self { return tap(static::make(...$attributes), function ($instance) { @@ -328,8 +344,9 @@ public static function createDraft(...$attributes): self public function setPublisher(): static { - if ($this->{$this->getPublisherColumns()['id']} === null && LaravelDrafts::getCurrentUser()) { - $this->publisher()->associate(LaravelDrafts::getCurrentUser()); + $currentUser = LaravelDrafts::getCurrentUser(); + if ($this->{$this->getPublisherColumns()['id']} === null && $currentUser instanceof Model) { + $this->publisher()->associate($currentUser); } return $this; @@ -339,7 +356,7 @@ public function pruneRevisions(): void { self::withoutEvents(function (): void { $revisionsToKeep = $this->revisions() - ->orderByDesc($this->getUpdatedAtColumn()) + ->orderByDesc($this->getUpdatedAtColumn() ?? 'updated_at') ->onlyDrafts() ->withoutCurrent() ->take(config('drafts.revisions.keep')) @@ -358,6 +375,9 @@ public function pruneRevisions(): void * Get the name of the "publisher" relation columns. */ #[ArrayShape(['id' => "string", 'type' => "string"])] + /** + * @return array{id: string, type: string} + */ public function getPublisherColumns(): array { return [ @@ -372,6 +392,8 @@ public function getPublisherColumns(): array /** * Get the fully qualified "publisher" relation columns. + * + * @return array{id: string, type: string} */ public function getQualifiedPublisherColumns(): array { @@ -403,16 +425,25 @@ public function isCurrent(): bool |-------------------------------------------------------------------------- */ + /** + * @return HasMany + */ public function revisions(): HasMany { return $this->hasMany(static::class, $this->getUuidColumn(), $this->getUuidColumn())->withDrafts(); } - public function drafts() + /** + * @return HasMany + */ + public function drafts(): HasMany { return $this->revisions()->current()->onlyDrafts(); } + /** + * @return MorphTo + */ public function publisher(): MorphTo { return $this->morphTo(config('drafts.column_names.publisher_morph_name')); @@ -453,7 +484,10 @@ public function scopeWithoutSelf(Builder $query): void |-------------------------------------------------------------------------- */ - public function getDraftAttribute() + /** + * @return static|null + */ + public function getDraftAttribute(): ?self { if ($this->relationLoaded('drafts')) { return $this->drafts->first(); diff --git a/src/Concerns/Publishes.php b/src/Concerns/Publishes.php index a58134a..9254dbb 100644 --- a/src/Concerns/Publishes.php +++ b/src/Concerns/Publishes.php @@ -6,9 +6,9 @@ use Oddvalue\LaravelDrafts\Scopes\PublishingScope; /** - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withPublished(bool $withPublished = true) - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder onlyPublished() - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutPublished() + * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withPublished(bool $withPublished = true) + * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder onlyPublished() + * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutPublished() */ trait Publishes { diff --git a/src/Http/Middleware/WithDraftsMiddleware.php b/src/Http/Middleware/WithDraftsMiddleware.php index c3f6a1f..9077a00 100644 --- a/src/Http/Middleware/WithDraftsMiddleware.php +++ b/src/Http/Middleware/WithDraftsMiddleware.php @@ -8,6 +8,7 @@ class WithDraftsMiddleware { + /** @phpstan-ignore missingType.return */ public function handle(Request $request, Closure $next) { LaravelDrafts::withDrafts(); diff --git a/src/Scopes/PublishingScope.php b/src/Scopes/PublishingScope.php index 58124d3..2e062ac 100644 --- a/src/Scopes/PublishingScope.php +++ b/src/Scopes/PublishingScope.php @@ -22,6 +22,7 @@ public function apply(Builder $builder, Model $model): void return; } + /** @phpstan-ignore method.notFound */ $builder->where($model->getQualifiedIsPublishedColumn(), 1); } diff --git a/tests/app/Models/Post.php b/tests/app/Models/Post.php index bafdeae..9f68856 100644 --- a/tests/app/Models/Post.php +++ b/tests/app/Models/Post.php @@ -13,6 +13,7 @@ /** * @mixes \Oddvalue\LaravelDrafts\Concerns\HasDrafts + * @phpstan-use HasFactory */ class Post extends Model { @@ -21,30 +22,46 @@ class Post extends Model protected $fillable = ['title']; + /** @var array */ protected array $draftableRelations = []; protected $table = 'posts'; + /** + * @param array $draftableRelations + */ public function setDraftableRelations(array $draftableRelations): void { $this->draftableRelations = $draftableRelations; } + /** + * @return HasMany + */ public function sections(): HasMany { return $this->hasMany(PostSection::class); } + /** + * @return BelongsToMany + */ public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class); } + /** + * @return MorphToMany + */ public function morphToTags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); } + /** + * @return HasOne + */ public function section(): HasOne { return $this->hasOne(PostSection::class); diff --git a/tests/app/Models/PostSection.php b/tests/app/Models/PostSection.php index b3430b1..d5c0870 100644 --- a/tests/app/Models/PostSection.php +++ b/tests/app/Models/PostSection.php @@ -3,14 +3,22 @@ namespace Oddvalue\LaravelDrafts\Tests\app\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Oddvalue\LaravelDrafts\Database\Factories\PostSectionFactory; +/** + * @phpstan-use HasFactory + */ class PostSection extends \Illuminate\Database\Eloquent\Model { use HasFactory; protected $guarded = []; - public function post() + /** + * @return BelongsTo + */ + public function post(): BelongsTo { return $this->belongsTo(Post::class); } diff --git a/tests/app/Models/SoftDeletingPost.php b/tests/app/Models/SoftDeletingPost.php index c2004d7..6c9f3ab 100644 --- a/tests/app/Models/SoftDeletingPost.php +++ b/tests/app/Models/SoftDeletingPost.php @@ -5,6 +5,9 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Oddvalue\LaravelDrafts\Database\Factories\SoftDeletingPostFactory; +/** + * @use \Illuminate\Database\Eloquent\Factories\HasFactory + */ class SoftDeletingPost extends Post { use SoftDeletes; diff --git a/tests/app/Models/Tag.php b/tests/app/Models/Tag.php index da40ab2..9762e83 100644 --- a/tests/app/Models/Tag.php +++ b/tests/app/Models/Tag.php @@ -3,14 +3,22 @@ namespace Oddvalue\LaravelDrafts\Tests\app\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\MorphTo; +use Oddvalue\LaravelDrafts\Database\Factories\TagFactory; +/** + * @phpstan-use HasFactory + */ class Tag extends \Illuminate\Database\Eloquent\Model { use HasFactory; protected $guarded = []; - public function taggables() + /** + * @return MorphTo<\Illuminate\Database\Eloquent\Model, $this> + */ + public function taggables(): MorphTo { return $this->morphTo('taggable'); } From e1471afa530896576ce51ebacaa4222b0ec0917d Mon Sep 17 00:00:00 2001 From: jim Date: Thu, 4 Dec 2025 22:17:04 +0000 Subject: [PATCH 2/3] Add PHPDoc annotations and improve PHPStan configuration --- database/factories/PostFactory.php | 5 +- database/factories/PostSectionFactory.php | 5 +- .../factories/SoftDeletingPostFactory.php | 4 +- database/factories/TagFactory.php | 5 +- phpstan-baseline.neon | 235 ------------------ phpstan.neon.dist | 79 +++++- src/Concerns/HasDrafts.php | 92 +++++-- src/LaravelDrafts.php | 10 +- src/LaravelDraftsServiceProvider.php | 55 ++-- src/Scopes/PublishingScope.php | 24 +- tests/app/Models/Post.php | 2 +- tests/app/Models/PostSection.php | 2 +- tests/app/Models/Tag.php | 2 +- 13 files changed, 223 insertions(+), 297 deletions(-) delete mode 100644 phpstan-baseline.neon diff --git a/database/factories/PostFactory.php b/database/factories/PostFactory.php index 61e1691..ffa9011 100644 --- a/database/factories/PostFactory.php +++ b/database/factories/PostFactory.php @@ -12,9 +12,10 @@ class PostFactory extends \Illuminate\Database\Eloquent\Factories\Factory protected $model = Post::class; /** - * @inheritDoc + * @return array + * @phpstan-ignore method.childReturnType */ - public function definition() + public function definition(): array { return [ 'title' => $this->faker->sentence, diff --git a/database/factories/PostSectionFactory.php b/database/factories/PostSectionFactory.php index d903520..b5f83f9 100644 --- a/database/factories/PostSectionFactory.php +++ b/database/factories/PostSectionFactory.php @@ -12,9 +12,10 @@ class PostSectionFactory extends \Illuminate\Database\Eloquent\Factories\Factory protected $model = PostSection::class; /** - * @inheritDoc + * @return array + * @phpstan-ignore method.childReturnType */ - public function definition() + public function definition(): array { return [ 'content' => $this->faker->paragraph, diff --git a/database/factories/SoftDeletingPostFactory.php b/database/factories/SoftDeletingPostFactory.php index 957b96c..2241648 100644 --- a/database/factories/SoftDeletingPostFactory.php +++ b/database/factories/SoftDeletingPostFactory.php @@ -9,9 +9,9 @@ class SoftDeletingPostFactory extends PostFactory protected $model = SoftDeletingPost::class; /** - * @inheritDoc + * @return array */ - public function definition() + public function definition(): array { return [ 'title' => $this->faker->sentence, diff --git a/database/factories/TagFactory.php b/database/factories/TagFactory.php index 7d484bf..ab70e1d 100644 --- a/database/factories/TagFactory.php +++ b/database/factories/TagFactory.php @@ -12,9 +12,10 @@ class TagFactory extends \Illuminate\Database\Eloquent\Factories\Factory protected $model = Tag::class; /** - * @inheritDoc + * @return array + * @phpstan-ignore method.childReturnType */ - public function definition() + public function definition(): array { return [ 'name' => $this->faker->word(), diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon deleted file mode 100644 index 05cffed..0000000 --- a/phpstan-baseline.neon +++ /dev/null @@ -1,235 +0,0 @@ -parameters: - ignoreErrors: - - - message: '#^Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder\:\:getModel\(\)\.$#' - identifier: method.notFound - count: 2 - path: src/Scopes/PublishingScope.php - - - - message: '#^Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder\:\:withDrafts\(\)\.$#' - identifier: method.notFound - count: 1 - path: src/Scopes/PublishingScope.php - - - - message: '#^Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder\:\:withoutDrafts\(\)\.$#' - identifier: method.notFound - count: 1 - path: src/Scopes/PublishingScope.php - - - - message: '#^Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder\:\:withoutGlobalScope\(\)\.$#' - identifier: method.notFound - count: 3 - path: src/Scopes/PublishingScope.php - - - - message: '#^Access to undefined constant static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\:\:IS_CURRENT\.$#' - identifier: classConstant.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Access to undefined constant static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\:\:IS_PUBLISHED\.$#' - identifier: classConstant.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Access to undefined constant static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\:\:PUBLISHED_AT\.$#' - identifier: classConstant.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Access to undefined constant static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\:\:PUBLISHER_ID\.$#' - identifier: classConstant.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Access to undefined constant static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\:\:PUBLISHER_TYPE\.$#' - identifier: classConstant.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Access to undefined constant static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\:\:UUID\.$#' - identifier: classConstant.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Attribute class JetBrains\\PhpStorm\\ArrayShape does not exist\.$#' - identifier: attribute.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder\:\:withDrafts\(\)\.$#' - identifier: method.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:generateUuid\(\)\.$#' - identifier: method.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:getIsCurrentColumn\(\)\.$#' - identifier: method.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:getIsPublishedColumn\(\)\.$#' - identifier: method.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:newRevision\(\)\.$#' - identifier: method.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:publish\(\)\.$#' - identifier: method.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:revisions\(\)\.$#' - identifier: method.notFound - count: 3 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:setLive\(\)\.$#' - identifier: method.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:setPublisher\(\)\.$#' - identifier: method.notFound - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\\:\:onlyDrafts\(\)\.$#' - identifier: method.notFound - count: 2 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\\:\:published\(\)\.$#' - identifier: method.notFound - count: 2 - path: tests/app/Models/Post.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany\\:\:withDrafts\(\)\.$#' - identifier: method.notFound - count: 3 - path: tests/app/Models/Post.php - - - - message: '#^Call to function property_exists\(\) with \$this\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\) and ''draftableRelations'' will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Using nullsafe method call on non\-nullable type mixed\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Method Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:getPublisherColumns\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: src/Concerns/HasDrafts.php - - - - message: '#^Method Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:getDraftAttribute\(\) should return static\(Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\)\|null but returns Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\|null\.$#' - identifier: return.type - count: 3 - path: src/Concerns/HasDrafts.php - - - - message: '#^Class Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post uses generic trait Illuminate\\Database\\Eloquent\\Factories\\HasFactory but does not specify its types\: TFactory$#' - identifier: missingType.generics - count: 1 - path: tests/app/Models/Post.php - - - - message: '#^Class Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\PostSection uses generic trait Illuminate\\Database\\Eloquent\\Factories\\HasFactory but does not specify its types\: TFactory$#' - identifier: missingType.generics - count: 1 - path: tests/app/Models/PostSection.php - - - - message: '#^Class Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Tag uses generic trait Illuminate\\Database\\Eloquent\\Factories\\HasFactory but does not specify its types\: TFactory$#' - identifier: missingType.generics - count: 1 - path: tests/app/Models/Tag.php - - - - message: '#^Method Oddvalue\\LaravelDrafts\\Database\\Factories\\PostFactory\:\:definition\(\) should return array\ but returns array\\.$#' - identifier: return.type - count: 1 - path: database/factories/PostFactory.php - - - - message: '#^Method Oddvalue\\LaravelDrafts\\Database\\Factories\\PostSectionFactory\:\:definition\(\) should return array\ but returns array\\.$#' - identifier: return.type - count: 1 - path: database/factories/PostSectionFactory.php - - - - message: '#^Method Oddvalue\\LaravelDrafts\\Database\\Factories\\SoftDeletingPostFactory\:\:definition\(\) should return array\ but returns array\\.$#' - identifier: return.type - count: 1 - path: database/factories/SoftDeletingPostFactory.php - - - - message: '#^Method Oddvalue\\LaravelDrafts\\Database\\Factories\\TagFactory\:\:definition\(\) should return array\ but returns array\\.$#' - identifier: return.type - count: 1 - path: database/factories/TagFactory.php - - - - message: '#^Cannot call method getDraftableAttributes\(\) on class\-string\|object\.$#' - identifier: method.nonObject - count: 2 - path: src/Concerns/HasDrafts.php - - - - message: '#^Parameter \#2 \$callback of static method Illuminate\\Database\\Eloquent\\Model\:\:registerModelEvent\(\) expects array\|\(callable\(\)\: mixed\)\|class\-string\|Illuminate\\Events\\QueuedClosure, Closure\|string given\.$#' - identifier: argument.type - count: 2 - path: src/Concerns/HasDrafts.php - - - - message: '#^Method Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\Post\:\:getQualifiedPublisherColumns\(\) should return array\{id\: string, type\: string\} but returns array\\.$#' - identifier: return.type - count: 1 - path: src/Concerns/HasDrafts.php - - - - message: '#^Parameter \#2 \$callback of static method Illuminate\\Database\\Eloquent\\Model\:\:registerModelEvent\(\) expects array\|\(callable\(\)\: mixed\)\|class\-string\|Illuminate\\Events\\QueuedClosure, Closure\|string given\.$#' - identifier: argument.type - count: 2 - path: src/Concerns/Publishes.php - - - - message: '#^Cannot access offset ''Illuminate\\\\Contracts\\\\Http\\\\Kernel'' on Illuminate\\Contracts\\Foundation\\Application\.$#' - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: src/LaravelDraftsServiceProvider.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index aadfafb..5758f72 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,8 +1,5 @@ -includes: - - phpstan-baseline.neon - parameters: - level: 8 + level: 10 paths: - src - config @@ -11,3 +8,77 @@ parameters: tmpDir: build/phpstan checkOctaneCompatibility: true checkModelProperties: true + ignoreErrors: + # Trait method calls in closures - PHPStan doesn't recognize trait methods on $model parameter + - + message: '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::(getIsCurrentColumn|setPublisher|generateUuid|getIsPublishedColumn|publish|newRevision|setLive|revisions|getUuidColumn|isCurrent|drafts|getQualifiedIsPublishedColumn)\(\)#' + paths: + - src/Concerns/*.php + - src/Scopes/*.php + reportUnmatched: false + # Method chaining on dynamic relations + - + message: '#Cannot call method (current|excludeRevision|withDrafts|withoutCurrent|onlyDrafts|published|merge|pluck|take|whereNotIn|delete|get|each|sync|create|getAttributes|getDraftableAttributes|where|first|replicate) on mixed#' + paths: + - src/Concerns/*.php + reportUnmatched: false + # Access to undefined constants in trait context + - + message: '#Access to undefined constant#' + paths: + - src/Concerns/*.php + reportUnmatched: false + # Method return types in trait context + - + message: '#Method Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\[A-Za-z]+::[a-zA-Z]+\(\) (should return|return type)#' + paths: + - src/Concerns/*.php + reportUnmatched: false + # HasFactory generic type - these models define factory types via newFactory() method + - + message: '#Class Oddvalue\\LaravelDrafts\\Tests\\app\\Models\\[A-Za-z]+ uses generic trait Illuminate\\Database\\Eloquent\\Factories\\HasFactory but does not specify its types: TFactory#' + paths: + - tests/app/Models/*.php + reportUnmatched: false + # ArrayShape attribute + - + message: '#Attribute class JetBrains\\PhpStorm\\ArrayShape does not exist#' + paths: + - src/Concerns/*.php + reportUnmatched: false + # Argument type issues in trait context + - + message: '#Parameter .* expects .*, (mixed|array|Closure\|string|class-string\|object) given#' + paths: + - src/Concerns/*.php + reportUnmatched: false + # Method not found errors in Scopes + - + message: '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::(withDrafts|withoutDrafts)#' + paths: + - src/Scopes/*.php + reportUnmatched: false + # withoutGlobalScope argument type in macros + - + message: '#Parameter \#1 \$scope of method Illuminate\\Database\\Eloquent\\Builder.*::withoutGlobalScope\(\) expects.*Illuminate\\Database\\Eloquent\\Builder.*given#' + paths: + - src/Scopes/*.php + reportUnmatched: false + # HasMany relation methods + - + message: '#Call to an undefined method Illuminate\\Database\\Eloquent\\Relations\\HasMany.*::(withDrafts|onlyDrafts|published)\(\)#' + paths: + - src/Concerns/*.php + reportUnmatched: false + # array_map callback type + - + message: '#Parameter \#1 \$callback of function array_map expects#' + paths: + - src/Concerns/*.php + reportUnmatched: false + # where() argument types in scopes + - + message: '#Parameter \#1 \$column of method Illuminate\\Database\\Eloquent\\Builder.*::where\(\) expects#' + paths: + - src/Scopes/*.php + reportUnmatched: false diff --git a/src/Concerns/HasDrafts.php b/src/Concerns/HasDrafts.php index 7a75e4f..20ebf67 100644 --- a/src/Concerns/HasDrafts.php +++ b/src/Concerns/HasDrafts.php @@ -2,7 +2,8 @@ namespace Oddvalue\LaravelDrafts\Concerns; -use Illuminate\Contracts\Database\Query\Builder; +use Illuminate\Contracts\Database\Query\Builder as QueryBuilder; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -18,6 +19,8 @@ * @method static Builder | Model current() * @method static Builder | Model withoutCurrent() * @method static Builder | Model excludeRevision(int | Model $exclude) + * + * @mixin Model */ trait HasDrafts { @@ -46,39 +49,50 @@ public static function bootHasDrafts(): void { static::addGlobalScope('onlyCurrentInPreviewMode', static function (Builder $builder): void { if (LaravelDrafts::isPreviewModeEnabled()) { + /** @phpstan-ignore method.notFound */ $builder->current(); } }); static::creating(function (Model $model): void { + /** @phpstan-ignore method.notFound */ $model->{$model->getIsCurrentColumn()} = true; + /** @phpstan-ignore method.notFound */ $model->setPublisher(); + /** @phpstan-ignore method.notFound */ $model->generateUuid(); + /** @phpstan-ignore method.notFound */ if ($model->{$model->getIsPublishedColumn()} !== false) { + /** @phpstan-ignore method.notFound */ $model->publish(); } }); static::updating(function (Model $model): void { + /** @phpstan-ignore method.notFound */ $model->newRevision(); }); static::publishing(function (Model $model): void { + /** @phpstan-ignore method.notFound */ $model->setLive(); }); static::deleted(function (Model $model): void { + /** @phpstan-ignore method.notFound, method.nonObject */ $model->revisions()->delete(); }); if (method_exists(static::class, 'restored')) { static::restored(function (Model $model): void { + /** @phpstan-ignore method.notFound, method.nonObject */ $model->revisions()->restore(); }); } if (method_exists(static::class, 'forceDeleted')) { static::forceDeleted(function (Model $model): void { + /** @phpstan-ignore method.notFound, method.nonObject */ $model->revisions()->forceDelete(); }); } @@ -92,6 +106,7 @@ protected function newRevision(): void // This model has been set not to create a revision || $this->shouldCreateRevision() === false // The record is being soft deleted or restored + /** @phpstan-ignore argument.type */ || $this->isDirty(method_exists($this, 'getDeletedAtColumn') ? $this->getDeletedAtColumn() : 'deleted_at') // A listener of the creatingRevision event returned false || $this->fireModelEvent('creatingRevision') === false @@ -159,18 +174,17 @@ public function setCurrent(): void return; } - $this->revisions() - ->withDrafts() - ->current() - ->excludeRevision($this) - ->update([$this->getIsCurrentColumn() => false]); + // @phpstan-ignore-next-line method.notFound, method.nonObject + $this->revisions()->withDrafts()->current()->excludeRevision($this)->update([$this->getIsCurrentColumn() => false]); }); } public function setLive(): void { + /** @phpstan-ignore method.notFound, method.nonObject */ $published = $this->revisions()->published()->first(); + /** @phpstan-ignore argument.type */ if (! $published || $this->is($published)) { $this->{$this->getPublishedAtColumn()} ??= now(); $this->{$this->getIsPublishedColumn()} = true; @@ -179,12 +193,16 @@ public function setLive(): void return; } + /** @phpstan-ignore method.nonObject, nullsafe.neverNull */ $oldAttributes = $published?->getDraftableAttributes() ?? []; $newAttributes = $this->getDraftableAttributes(); + /** @phpstan-ignore argument.type */ Arr::forget($oldAttributes, $this->getKeyName()); Arr::forget($newAttributes, $this->getKeyName()); + /** @phpstan-ignore method.nonObject */ $published->forceFill($newAttributes); + /** @phpstan-ignore argument.type */ $this->forceFill($oldAttributes); static::saved(function (Model $model) use ($published): void { @@ -192,11 +210,16 @@ public function setLive(): void return; } + /** @phpstan-ignore method.nonObject */ $published->{$this->getIsPublishedColumn()} = true; + /** @phpstan-ignore method.nonObject */ $published->{$this->getPublishedAtColumn()} ??= now(); + /** @phpstan-ignore method.nonObject */ $published->setCurrent(); + /** @phpstan-ignore method.nonObject */ $published->saveQuietly(); + /** @phpstan-ignore argument.type */ $this->replicateAndAssociateDraftableRelations($published); }); @@ -214,30 +237,38 @@ public function replicateAndAssociateDraftableRelations(Model $published): void switch (true) { case $relation instanceof HasOne: if ($related = $this->{$relationName}) { + /** @phpstan-ignore method.nonObject */ $replicated = $related->replicate(); + /** @phpstan-ignore argument.type */ $method = method_exists($replicated, 'getDraftableAttributes') ? 'getDraftableAttributes' : 'getAttributes'; + // @phpstan-ignore-next-line method.nonObject $published->{$relationName}()->create($replicated->$method()); } break; case $relation instanceof HasMany: + // @phpstan-ignore-next-line method.nonObject $this->{$relationName}()->get()->each(function ($model) use ($published, $relationName): void { + // @phpstan-ignore-next-line method.nonObject $replicated = $model->replicate(); + /** @phpstan-ignore argument.type */ $method = method_exists($replicated, 'getDraftableAttributes') ? 'getDraftableAttributes' : 'getAttributes'; + // @phpstan-ignore-next-line method.nonObject $published->{$relationName}()->create($replicated->$method()); }); break; case $relation instanceof MorphToMany: case $relation instanceof BelongsToMany: + // @phpstan-ignore-next-line method.nonObject $published->{$relationName}()->sync($this->{$relationName}()->pluck('id')); break; @@ -250,6 +281,7 @@ public function replicateAndAssociateDraftableRelations(Model $published): void */ public function getDraftableRelations(): array { + /** @phpstan-ignore function.alreadyNarrowedType */ return property_exists($this, 'draftableRelations') ? $this->draftableRelations : []; } @@ -332,10 +364,13 @@ public function updateAsDraft(array $attributes = [], array $options = []): bool /** * @param array ...$attributes + * @return static */ public static function createDraft(...$attributes): self { + /** @phpstan-ignore return.type */ return tap(static::make(...$attributes), function ($instance) { + /** @phpstan-ignore argument.type */ $instance->{$instance->getIsPublishedColumn()} = false; return $instance->save(); @@ -355,19 +390,11 @@ public function setPublisher(): static public function pruneRevisions(): void { self::withoutEvents(function (): void { - $revisionsToKeep = $this->revisions() - ->orderByDesc($this->getUpdatedAtColumn() ?? 'updated_at') - ->onlyDrafts() - ->withoutCurrent() - ->take(config('drafts.revisions.keep')) - ->pluck('id') - ->merge($this->revisions()->current()->pluck('id')) - ->merge($this->revisions()->published()->pluck('id')); - - $this->revisions() - ->withDrafts() - ->whereNotIn('id', $revisionsToKeep) - ->delete(); + // @phpstan-ignore-next-line method.notFound, method.nonObject + $revisionsToKeep = $this->revisions()->orderByDesc($this->getUpdatedAtColumn() ?? 'updated_at')->onlyDrafts()->withoutCurrent()->take(config('drafts.revisions.keep'))->pluck('id')->merge($this->revisions()->current()->pluck('id'))->merge($this->revisions()->published()->pluck('id')); + + // @phpstan-ignore-next-line method.notFound, method.nonObject + $this->revisions()->withDrafts()->whereNotIn('id', $revisionsToKeep)->delete(); }); } @@ -380,13 +407,16 @@ public function pruneRevisions(): void */ public function getPublisherColumns(): array { + /** @var string $morphName */ + $morphName = config('drafts.column_names.publisher_morph_name', 'publisher'); + return [ 'id' => defined(static::class.'::PUBLISHER_ID') ? static::PUBLISHER_ID - : config('drafts.column_names.publisher_morph_name', 'publisher') . '_id', + : $morphName . '_id', 'type' => defined(static::class.'::PUBLISHER_TYPE') ? static::PUBLISHER_TYPE - : config('drafts.column_names.publisher_morph_name', 'publisher') . '_type', + : $morphName . '_type', ]; } @@ -446,7 +476,10 @@ public function drafts(): HasMany */ public function publisher(): MorphTo { - return $this->morphTo(config('drafts.column_names.publisher_morph_name')); + /** @var string|null $morphName */ + $morphName = config('drafts.column_names.publisher_morph_name'); + + return $this->morphTo($morphName); } /* @@ -455,16 +488,26 @@ public function publisher(): MorphTo |-------------------------------------------------------------------------- */ + /** + * @param Builder $query + */ public function scopeCurrent(Builder $query): void { + /** @phpstan-ignore method.notFound, method.nonObject */ $query->withDrafts()->where($this->getIsCurrentColumn(), true); } + /** + * @param Builder $query + */ public function scopeWithoutCurrent(Builder $query): void { $query->where($this->getIsCurrentColumn(), false); } + /** + * @param Builder $query + */ public function scopeExcludeRevision(Builder $query, int | Model $exclude): void { $query->where($this->getKeyName(), '!=', is_int($exclude) ? $exclude : $exclude->getKey()); @@ -472,9 +515,11 @@ public function scopeExcludeRevision(Builder $query, int | Model $exclude): void /** * @deprecated This doesn't actually work, will be removed in next version + * @param Builder $query */ public function scopeWithoutSelf(Builder $query): void { + /** @phpstan-ignore argument.type */ $query->where('id', '!=', $this->id); } @@ -490,13 +535,16 @@ public function scopeWithoutSelf(Builder $query): void public function getDraftAttribute(): ?self { if ($this->relationLoaded('drafts')) { + /** @phpstan-ignore return.type */ return $this->drafts->first(); } if ($this->relationLoaded('revisions')) { + /** @phpstan-ignore return.type */ return $this->revisions->firstWhere($this->getIsCurrentColumn(), true); } + /** @phpstan-ignore return.type */ return $this->drafts()->first(); } diff --git a/src/LaravelDrafts.php b/src/LaravelDrafts.php index 586f603..da67c12 100755 --- a/src/LaravelDrafts.php +++ b/src/LaravelDrafts.php @@ -12,7 +12,10 @@ class LaravelDrafts public function getCurrentUser(): ?Authenticatable { - return Auth::guard(config('drafts.auth.guard'))->user(); + /** @var string|null $guard */ + $guard = config('drafts.auth.guard'); + + return Auth::guard($guard)->user(); } public function previewMode(bool $previewMode = true): void @@ -27,7 +30,10 @@ public function disablePreviewMode(): void public function isPreviewModeEnabled(): bool { - return Session::get('drafts.preview', false); + /** @var bool $preview */ + $preview = Session::get('drafts.preview', false); + + return $preview; } public function withDrafts(bool $withDrafts = true): void diff --git a/src/LaravelDraftsServiceProvider.php b/src/LaravelDraftsServiceProvider.php index 8ff5f3d..c081389 100644 --- a/src/LaravelDraftsServiceProvider.php +++ b/src/LaravelDraftsServiceProvider.php @@ -28,6 +28,7 @@ public function packageRegistered(): void { $this->app->singleton(LaravelDrafts::class, fn (): LaravelDrafts => new LaravelDrafts()); + /** @phpstan-ignore offsetAccess.nonOffsetAccessible, method.nonObject */ $this->app[Kernel::class]->prependToMiddlewarePriority(WithDraftsMiddleware::class); Blueprint::macro('drafts', function ( @@ -37,19 +38,24 @@ public function packageRegistered(): void ?string $isCurrent = null, ?string $publisherMorphName = null, ): void { - $uuid ??= config('drafts.column_names.uuid', 'uuid'); - $publishedAt ??= config('drafts.column_names.published_at', 'published_at'); - $isPublished ??= config('drafts.column_names.is_published', 'is_published'); - $isCurrent ??= config('drafts.column_names.is_current', 'is_current'); - $publisherMorphName ??= config('drafts.column_names.publisher_morph_name', 'publisher_morph_name'); + /** @var string $uuidCol */ + $uuidCol = $uuid ?? config('drafts.column_names.uuid', 'uuid'); + /** @var string $publishedAtCol */ + $publishedAtCol = $publishedAt ?? config('drafts.column_names.published_at', 'published_at'); + /** @var string $isPublishedCol */ + $isPublishedCol = $isPublished ?? config('drafts.column_names.is_published', 'is_published'); + /** @var string $isCurrentCol */ + $isCurrentCol = $isCurrent ?? config('drafts.column_names.is_current', 'is_current'); + /** @var string $publisherMorphNameCol */ + $publisherMorphNameCol = $publisherMorphName ?? config('drafts.column_names.publisher_morph_name', 'publisher_morph_name'); - $this->uuid($uuid)->nullable(); - $this->timestamp($publishedAt)->nullable(); - $this->boolean($isPublished)->default(false); - $this->boolean($isCurrent)->default(false); - $this->nullableMorphs($publisherMorphName); + $this->uuid($uuidCol)->nullable(); + $this->timestamp($publishedAtCol)->nullable(); + $this->boolean($isPublishedCol)->default(false); + $this->boolean($isCurrentCol)->default(false); + $this->nullableMorphs($publisherMorphNameCol); - $this->index([$uuid, $isPublished, $isCurrent]); + $this->index([$uuidCol, $isPublishedCol, $isCurrentCol]); }); Blueprint::macro('dropDrafts', function ( @@ -59,20 +65,25 @@ public function packageRegistered(): void ?string $isCurrent = null, ?string $publisherMorphName = null, ): void { - $uuid ??= config('drafts.column_names.uuid', 'uuid'); - $publishedAt ??= config('drafts.column_names.published_at', 'published_at'); - $isPublished ??= config('drafts.column_names.is_published', 'is_published'); - $isCurrent ??= config('drafts.column_names.is_current', 'is_current'); - $publisherMorphName ??= config('drafts.column_names.publisher_morph_name', 'publisher_morph_name'); + /** @var string $uuidCol */ + $uuidCol = $uuid ?? config('drafts.column_names.uuid', 'uuid'); + /** @var string $publishedAtCol */ + $publishedAtCol = $publishedAt ?? config('drafts.column_names.published_at', 'published_at'); + /** @var string $isPublishedCol */ + $isPublishedCol = $isPublished ?? config('drafts.column_names.is_published', 'is_published'); + /** @var string $isCurrentCol */ + $isCurrentCol = $isCurrent ?? config('drafts.column_names.is_current', 'is_current'); + /** @var string $publisherMorphNameCol */ + $publisherMorphNameCol = $publisherMorphName ?? config('drafts.column_names.publisher_morph_name', 'publisher_morph_name'); - $this->dropIndex([$uuid, $isPublished, $isCurrent]); - $this->dropMorphs($publisherMorphName); + $this->dropIndex([$uuidCol, $isPublishedCol, $isCurrentCol]); + $this->dropMorphs($publisherMorphNameCol); $this->dropColumn([ - $uuid, - $publishedAt, - $isPublished, - $isCurrent, + $uuidCol, + $publishedAtCol, + $isPublishedCol, + $isCurrentCol, ]); }); diff --git a/src/Scopes/PublishingScope.php b/src/Scopes/PublishingScope.php index 2e062ac..e04faff 100644 --- a/src/Scopes/PublishingScope.php +++ b/src/Scopes/PublishingScope.php @@ -2,7 +2,8 @@ namespace Oddvalue\LaravelDrafts\Scopes; -use Illuminate\Contracts\Database\Query\Builder; +use Illuminate\Contracts\Database\Query\Builder as QueryBuilder; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; use Oddvalue\LaravelDrafts\Facades\LaravelDrafts; @@ -16,6 +17,9 @@ class PublishingScope implements Scope */ protected $extensions = [/*'Publish', 'Unpublish', 'Schedule', */'Published', 'WithDrafts', 'WithoutDrafts', 'OnlyDrafts']; + /** + * @param Builder $builder + */ public function apply(Builder $builder, Model $model): void { if (LaravelDrafts::isPreviewModeEnabled() || LaravelDrafts::isWithDraftsEnabled()) { @@ -26,6 +30,9 @@ public function apply(Builder $builder, Model $model): void $builder->where($model->getQualifiedIsPublishedColumn(), 1); } + /** + * @param Builder $builder + */ public function extend(Builder $builder): void { foreach ($this->extensions as $extension) { @@ -58,14 +65,21 @@ public function extend(Builder $builder): void // }); // } + /** + * @param Builder $builder + */ protected function addPublished(Builder $builder): void { $builder->macro( 'published', + /** @param Builder $builder */ fn (Builder $builder, $withoutDrafts = true) => $builder->withDrafts(! $withoutDrafts), ); } + /** + * @param Builder $builder + */ protected function addWithDrafts(Builder $builder): void { $builder->macro('withDrafts', function (Builder $builder, $withDrafts = true) { @@ -77,24 +91,32 @@ protected function addWithDrafts(Builder $builder): void }); } + /** + * @param Builder $builder + */ protected function addWithoutDrafts(Builder $builder): void { $builder->macro('withoutDrafts', function (Builder $builder): Builder { $model = $builder->getModel(); $builder->withoutGlobalScope($this) + /** @phpstan-ignore argument.type */ ->where($model->getQualifiedIsPublishedColumn(), 1); return $builder; }); } + /** + * @param Builder $builder + */ protected function addOnlyDrafts(Builder $builder): void { $builder->macro('onlyDrafts', function (Builder $builder): Builder { $model = $builder->getModel(); $builder->withoutGlobalScope($this) + /** @phpstan-ignore argument.type */ ->where($model->getQualifiedIsPublishedColumn(), 0); return $builder; diff --git a/tests/app/Models/Post.php b/tests/app/Models/Post.php index 9f68856..14c2a47 100644 --- a/tests/app/Models/Post.php +++ b/tests/app/Models/Post.php @@ -13,7 +13,7 @@ /** * @mixes \Oddvalue\LaravelDrafts\Concerns\HasDrafts - * @phpstan-use HasFactory + * @use HasFactory */ class Post extends Model { diff --git a/tests/app/Models/PostSection.php b/tests/app/Models/PostSection.php index d5c0870..a69ff4a 100644 --- a/tests/app/Models/PostSection.php +++ b/tests/app/Models/PostSection.php @@ -7,7 +7,7 @@ use Oddvalue\LaravelDrafts\Database\Factories\PostSectionFactory; /** - * @phpstan-use HasFactory + * @use HasFactory */ class PostSection extends \Illuminate\Database\Eloquent\Model { diff --git a/tests/app/Models/Tag.php b/tests/app/Models/Tag.php index 9762e83..3077eb6 100644 --- a/tests/app/Models/Tag.php +++ b/tests/app/Models/Tag.php @@ -7,7 +7,7 @@ use Oddvalue\LaravelDrafts\Database\Factories\TagFactory; /** - * @phpstan-use HasFactory + * @use HasFactory */ class Tag extends \Illuminate\Database\Eloquent\Model { From 5736cad5c50c5ad7d949c0d069d9cf02d2728156 Mon Sep 17 00:00:00 2001 From: jim Date: Thu, 4 Dec 2025 22:25:51 +0000 Subject: [PATCH 3/3] Run Rector to apply Laravel-focused upgrades and improve type safety --- composer.json | 1 + rector.php | 20 +++++++++++++++++++- src/Concerns/HasDrafts.php | 16 ++++++++-------- src/Concerns/Publishes.php | 12 +++++++----- src/Facades/LaravelDrafts.php | 3 ++- src/LaravelDraftsServiceProvider.php | 6 +++--- src/Scopes/PublishingScope.php | 1 - tests/ConfigTest.php | 15 ++++++++++----- tests/DraftableRelationsTest.php | 8 ++++---- tests/DraftsTest.php | 4 ++-- tests/LaravelDraftsTest.php | 4 +++- tests/MiddlewareTest.php | 2 +- tests/PublishingTest.php | 23 ++++++++++++----------- tests/TestCase.php | 17 +++++++++-------- tests/app/Models/PostSection.php | 3 ++- tests/app/Models/SoftDeletingPost.php | 3 ++- tests/app/Models/Tag.php | 5 +++-- tests/app/Models/User.php | 2 ++ 18 files changed, 90 insertions(+), 55 deletions(-) diff --git a/composer.json b/composer.json index b1298c7..69da44a 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ ], "require": { "php": "^8.0", + "driftingly/rector-laravel": "^2.1", "illuminate/contracts": "^11.0|^12.0", "spatie/laravel-package-tools": "^1.9.2" }, diff --git a/rector.php b/rector.php index 3cf3f1f..cd653e2 100644 --- a/rector.php +++ b/rector.php @@ -3,8 +3,27 @@ declare(strict_types=1); use Rector\Config\RectorConfig; +use RectorLaravel\Set\LaravelSetList; +use RectorLaravel\Set\LaravelSetProvider; return RectorConfig::configure() + ->withSetProviders(LaravelSetProvider::class) + ->withSets([ + LaravelSetList::LARAVEL_ARRAYACCESS_TO_METHOD_CALL, + LaravelSetList::LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL, + LaravelSetList::LARAVEL_CODE_QUALITY, + LaravelSetList::LARAVEL_COLLECTION, + LaravelSetList::LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME, + LaravelSetList::LARAVEL_ELOQUENT_MAGIC_METHOD_TO_QUERY_BUILDER, + LaravelSetList::LARAVEL_FACADE_ALIASES_TO_FULL_NAMES, + LaravelSetList::LARAVEL_FACTORIES, + LaravelSetList::LARAVEL_IF_HELPERS, + LaravelSetList::LARAVEL_LEGACY_FACTORIES_TO_CLASSES, + ]) + ->withImportNames( + removeUnusedImports: true, + ) + ->withComposerBased(laravel: true) ->withPaths([ __DIR__ . '/config', __DIR__ . '/src', @@ -18,5 +37,4 @@ typeDeclarations: true, privatization: true, earlyReturn: true, - strictBooleans: true, ); diff --git a/src/Concerns/HasDrafts.php b/src/Concerns/HasDrafts.php index 20ebf67..417d09c 100644 --- a/src/Concerns/HasDrafts.php +++ b/src/Concerns/HasDrafts.php @@ -2,7 +2,7 @@ namespace Oddvalue\LaravelDrafts\Concerns; -use Illuminate\Contracts\Database\Query\Builder as QueryBuilder; +use Closure; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -339,12 +339,12 @@ public function save(array $options = []): bool return parent::save($options); } - public static function savingAsDraft(string|\Closure $callback): void + public static function savingAsDraft(string|Closure $callback): void { static::registerModelEvent('savingAsDraft', $callback); } - public static function savedAsDraft(string|\Closure $callback): void + public static function savedAsDraft(string|Closure $callback): void { static::registerModelEvent('drafted', $callback); } @@ -491,7 +491,7 @@ public function publisher(): MorphTo /** * @param Builder $query */ - public function scopeCurrent(Builder $query): void + protected function scopeCurrent(Builder $query): void { /** @phpstan-ignore method.notFound, method.nonObject */ $query->withDrafts()->where($this->getIsCurrentColumn(), true); @@ -500,7 +500,7 @@ public function scopeCurrent(Builder $query): void /** * @param Builder $query */ - public function scopeWithoutCurrent(Builder $query): void + protected function scopeWithoutCurrent(Builder $query): void { $query->where($this->getIsCurrentColumn(), false); } @@ -508,7 +508,7 @@ public function scopeWithoutCurrent(Builder $query): void /** * @param Builder $query */ - public function scopeExcludeRevision(Builder $query, int | Model $exclude): void + protected function scopeExcludeRevision(Builder $query, int | Model $exclude): void { $query->where($this->getKeyName(), '!=', is_int($exclude) ? $exclude : $exclude->getKey()); } @@ -517,7 +517,7 @@ public function scopeExcludeRevision(Builder $query, int | Model $exclude): void * @deprecated This doesn't actually work, will be removed in next version * @param Builder $query */ - public function scopeWithoutSelf(Builder $query): void + protected function scopeWithoutSelf(Builder $query): void { /** @phpstan-ignore argument.type */ $query->where('id', '!=', $this->id); @@ -532,7 +532,7 @@ public function scopeWithoutSelf(Builder $query): void /** * @return static|null */ - public function getDraftAttribute(): ?self + protected function getDraftAttribute(): ?self { if ($this->relationLoaded('drafts')) { /** @phpstan-ignore return.type */ diff --git a/src/Concerns/Publishes.php b/src/Concerns/Publishes.php index 9254dbb..0c30ed1 100644 --- a/src/Concerns/Publishes.php +++ b/src/Concerns/Publishes.php @@ -2,13 +2,15 @@ namespace Oddvalue\LaravelDrafts\Concerns; +use Closure; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Oddvalue\LaravelDrafts\Scopes\PublishingScope; /** - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withPublished(bool $withPublished = true) - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder onlyPublished() - * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutPublished() + * @method static Builder|\Illuminate\Database\Query\Builder withPublished(bool $withPublished = true) + * @method static Builder|\Illuminate\Database\Query\Builder onlyPublished() + * @method static Builder|\Illuminate\Database\Query\Builder withoutPublished() */ trait Publishes { @@ -70,7 +72,7 @@ public function isPublished(): bool /** * Register a "published" model event callback with the dispatcher. */ - public static function publishing(string|\Closure $callback): void + public static function publishing(string|Closure $callback): void { static::registerModelEvent('publishing', $callback); } @@ -78,7 +80,7 @@ public static function publishing(string|\Closure $callback): void /** * Register a "softDeleted" model event callback with the dispatcher. */ - public static function published(string|\Closure $callback): void + public static function published(string|Closure $callback): void { static::registerModelEvent('published', $callback); } diff --git a/src/Facades/LaravelDrafts.php b/src/Facades/LaravelDrafts.php index 79a2ec6..7578d42 100644 --- a/src/Facades/LaravelDrafts.php +++ b/src/Facades/LaravelDrafts.php @@ -2,10 +2,11 @@ namespace Oddvalue\LaravelDrafts\Facades; +use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Facades\Facade; /** - * @method static \Illuminate\Contracts\Auth\Authenticatable getCurrentUser() + * @method static Authenticatable getCurrentUser() * @method static void previewMode(bool $previewMode = true) * @method static void disablePreviewMode() * @method static bool isPreviewModeEnabled() diff --git a/src/LaravelDraftsServiceProvider.php b/src/LaravelDraftsServiceProvider.php index c081389..cdff297 100644 --- a/src/LaravelDraftsServiceProvider.php +++ b/src/LaravelDraftsServiceProvider.php @@ -2,6 +2,7 @@ namespace Oddvalue\LaravelDrafts; +use Closure; use Illuminate\Contracts\Http\Kernel; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Route; @@ -28,8 +29,7 @@ public function packageRegistered(): void { $this->app->singleton(LaravelDrafts::class, fn (): LaravelDrafts => new LaravelDrafts()); - /** @phpstan-ignore offsetAccess.nonOffsetAccessible, method.nonObject */ - $this->app[Kernel::class]->prependToMiddlewarePriority(WithDraftsMiddleware::class); + $this->app->make(Kernel::class)->prependToMiddlewarePriority(WithDraftsMiddleware::class); Blueprint::macro('drafts', function ( ?string $uuid = null, @@ -87,7 +87,7 @@ public function packageRegistered(): void ]); }); - Route::macro('withDrafts', function (\Closure $routes): void { + Route::macro('withDrafts', function (Closure $routes): void { Route::middleware(WithDraftsMiddleware::class)->group($routes); }); } diff --git a/src/Scopes/PublishingScope.php b/src/Scopes/PublishingScope.php index e04faff..b7998b4 100644 --- a/src/Scopes/PublishingScope.php +++ b/src/Scopes/PublishingScope.php @@ -2,7 +2,6 @@ namespace Oddvalue\LaravelDrafts\Scopes; -use Illuminate\Contracts\Database\Query\Builder as QueryBuilder; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index 7ab7062..616f9a8 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -1,6 +1,10 @@ 'publisher_override', ], ]); - $post = Post::make(); + $post = Post::query()->make(); expect($post->getPublishedAtColumn())->toBe('published_at_override') ->and($post->getIsPublishedColumn())->toBe('is_published_override') @@ -48,7 +52,7 @@ it('honors column name overrides', function (): void { - $post = OverridePost::make([ + $post = OverridePost::query()->make([ ]); invade($post)->newRevision(); @@ -60,10 +64,11 @@ ->and($post->getPublisherColumns())->toBe(['id' => 'publisher_override_id', 'type' => 'publisher_override_type']); }); -class OverridePost extends \Illuminate\Database\Eloquent\Model +class OverridePost extends Model { - use \Oddvalue\LaravelDrafts\Concerns\HasDrafts; - use \Illuminate\Database\Eloquent\SoftDeletes; + use HasFactory; + use HasDrafts; + use SoftDeletes; public const PUBLISHED_AT = 'published_at_override'; diff --git a/tests/DraftableRelationsTest.php b/tests/DraftableRelationsTest.php index 2341306..479e38d 100644 --- a/tests/DraftableRelationsTest.php +++ b/tests/DraftableRelationsTest.php @@ -23,7 +23,7 @@ expect($draft->fresh()->sections)->toHaveCount(2) ->and($post->fresh()->sections)->toHaveCount(2) - ->and(PostSection::count())->toBe(4); + ->and(PostSection::query()->count())->toBe(4); }); it('can draft BelongsToMany relations', function (): void { @@ -44,7 +44,7 @@ expect($draft->fresh()->tags)->toHaveCount(2) ->and($post->fresh()->tags)->toHaveCount(2) - ->and(Tag::count())->toBe(2) + ->and(Tag::query()->count())->toBe(2) ->and(DB::table('post_tag')->count())->toBe(4); }); @@ -66,7 +66,7 @@ expect($draft->fresh()->morphToTags)->toHaveCount(2) ->and($post->fresh()->morphToTags)->toHaveCount(2) - ->and(Tag::count())->toBe(2) + ->and(Tag::query()->count())->toBe(2) ->and(DB::table('taggables')->count())->toBe(4); }); @@ -88,5 +88,5 @@ expect($draft->fresh()->section)->toBeInstanceOf(PostSection::class) ->and($post->fresh()->section)->toBeInstanceOf(PostSection::class) - ->and(PostSection::count())->toBe(2); + ->and(PostSection::query()->count())->toBe(2); }); diff --git a/tests/DraftsTest.php b/tests/DraftsTest.php index 3451d1a..8e8a358 100644 --- a/tests/DraftsTest.php +++ b/tests/DraftsTest.php @@ -118,12 +118,12 @@ $post->updateAsDraft(['title' => 'Baz']); $post->updateAsDraft(['title' => 'Qux']); - expect(Post::find($originalId)->title)->toBe('Foo') + expect(Post::query()->find($originalId)->title)->toBe('Foo') ->and(DB::table('posts')->count())->toBe(4); $post->publish()->save(); - expect(Post::find($originalId)->title)->toBe('Qux'); + expect(Post::query()->find($originalId)->title)->toBe('Qux'); }); it('can get all revisions excluding self', function (): void { diff --git a/tests/LaravelDraftsTest.php b/tests/LaravelDraftsTest.php index bbbb5ca..49531e9 100644 --- a/tests/LaravelDraftsTest.php +++ b/tests/LaravelDraftsTest.php @@ -1,5 +1,7 @@ assertEquals($this->testUser, \Oddvalue\LaravelDrafts\Facades\LaravelDrafts::getCurrentUser()); + $this->assertEquals($this->testUser, LaravelDrafts::getCurrentUser()); }); diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php index 1221304..936b0b1 100644 --- a/tests/MiddlewareTest.php +++ b/tests/MiddlewareTest.php @@ -7,7 +7,7 @@ use function Pest\Laravel\get; beforeEach(function (): void { - test()->post = Post::create(['title' => 'Hello World']); + test()->post = Post::query()->create(['title' => 'Hello World']); test()->draftPost = Post::createDraft(['title' => 'Hello World draft']); Route::middleware(['web'])->group(function (): void { diff --git a/tests/PublishingTest.php b/tests/PublishingTest.php index 7607396..8574a4a 100644 --- a/tests/PublishingTest.php +++ b/tests/PublishingTest.php @@ -1,5 +1,6 @@ freeze(); - Post::create(['title' => 'Hello World']); + Post::query()->create(['title' => 'Hello World']); Post::withDrafts()->first()->save(); $this->assertDatabaseHas('posts', [ 'published_at' => now()->toDateTimeString(), @@ -23,7 +24,7 @@ it('can publish a model', function (): void { testTime()->freeze(); - Post::make(['title' => 'Hello World'])->save(); + Post::query()->make(['title' => 'Hello World'])->save(); $this->assertDatabaseHas('posts', [ 'title' => 'Hello World', 'published_at' => now()->toDateTimeString(), @@ -68,27 +69,27 @@ }); it('can publish a draft that is not the current one', function (): void { - \Oddvalue\LaravelDrafts\Facades\LaravelDrafts::withDrafts(); + LaravelDrafts::withDrafts(); Post::factory()->create(['title' => 'a']); - $post = Post::where('title', 'a')->first(); + $post = Post::query()->where('title', 'a')->first(); $post->title = 'b'; $b = $post->saveAsDraft(); - $post = Post::where('title', 'b')->first(); + $post = Post::query()->where('title', 'b')->first(); $post->title = 'c'; $post->saveAsDraft(); - expect(Post::where('title', 'a')->first()->isPublished())->toBeTrue(); - expect(Post::where('title', 'c')->first()->isCurrent())->toBeTrue(); + expect(Post::query()->where('title', 'a')->first()->isPublished())->toBeTrue(); + expect(Post::query()->where('title', 'c')->first()->isCurrent())->toBeTrue(); - $draftB = Post::where('title', 'b')->first(); + $draftB = Post::query()->where('title', 'b')->first(); $draftB->setLive(); $draftB->save(); - expect(Post::where('title', 'a')->first()->isPublished())->toBeFalse(); - expect(Post::where('title', 'b')->first()->isPublished())->toBeTrue(); - expect(Post::current()->count())->toBe(1); + expect(Post::query()->where('title', 'a')->first()->isPublished())->toBeFalse(); + expect(Post::query()->where('title', 'b')->first()->isPublished())->toBeTrue(); + expect(Post::query()->current()->count())->toBe(1); }); diff --git a/tests/TestCase.php b/tests/TestCase.php index 1bb72e3..31f72fc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,7 @@ namespace Oddvalue\LaravelDrafts\Tests; +use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Schema\Blueprint; use Illuminate\Foundation\Application; @@ -49,12 +50,12 @@ protected function getEnvironmentSetUp($app) */ protected function setUpDatabase(Application $app) { - $app['db']->connection()->getSchemaBuilder()->create('users', function (Blueprint $table): void { + $app->make(ConnectionResolverInterface::class)->connection()->getSchemaBuilder()->create('users', function (Blueprint $table): void { $table->increments('id'); $table->string('email'); }); - $app['db']->connection()->getSchemaBuilder()->create('posts', function (Blueprint $table): void { + $app->make(ConnectionResolverInterface::class)->connection()->getSchemaBuilder()->create('posts', function (Blueprint $table): void { $table->increments('id'); $table->string('title'); $table->json('traits')->nullable(); @@ -62,30 +63,30 @@ protected function setUpDatabase(Application $app) $table->timestamps(); }); - $app['db']->connection()->getSchemaBuilder()->create('post_sections', function (Blueprint $table): void { + $app->make(ConnectionResolverInterface::class)->connection()->getSchemaBuilder()->create('post_sections', function (Blueprint $table): void { $table->increments('id'); $table->string('content'); $table->foreignIdFor(Post::class)->constrained(); $table->timestamps(); }); - $app['db']->connection()->getSchemaBuilder()->create('tags', function (Blueprint $table): void { + $app->make(ConnectionResolverInterface::class)->connection()->getSchemaBuilder()->create('tags', function (Blueprint $table): void { $table->increments('id'); $table->string('name'); $table->timestamps(); }); - $app['db']->connection()->getSchemaBuilder()->create('taggables', function (Blueprint $table): void { + $app->make(ConnectionResolverInterface::class)->connection()->getSchemaBuilder()->create('taggables', function (Blueprint $table): void { $table->foreignIdFor(Tag::class)->constrained(); $table->morphs('taggable'); }); - $app['db']->connection()->getSchemaBuilder()->create('post_tag', function (Blueprint $table): void { + $app->make(ConnectionResolverInterface::class)->connection()->getSchemaBuilder()->create('post_tag', function (Blueprint $table): void { $table->foreignIdFor(Tag::class)->constrained(); $table->foreignIdFor(Post::class)->constrained(); }); - $app['db']->connection()->getSchemaBuilder()->create('soft_deleting_posts', function (Blueprint $table): void { + $app->make(ConnectionResolverInterface::class)->connection()->getSchemaBuilder()->create('soft_deleting_posts', function (Blueprint $table): void { $table->increments('id'); $table->string('title'); $table->drafts(); @@ -93,7 +94,7 @@ protected function setUpDatabase(Application $app) $table->timestamps(); }); - $this->testUser = User::create(['email' => 'test@user.com']); + $this->testUser = User::query()->create(['email' => 'test@user.com']); Auth::login($this->testUser); } } diff --git a/tests/app/Models/PostSection.php b/tests/app/Models/PostSection.php index a69ff4a..87bedf6 100644 --- a/tests/app/Models/PostSection.php +++ b/tests/app/Models/PostSection.php @@ -3,13 +3,14 @@ namespace Oddvalue\LaravelDrafts\Tests\app\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Oddvalue\LaravelDrafts\Database\Factories\PostSectionFactory; /** * @use HasFactory */ -class PostSection extends \Illuminate\Database\Eloquent\Model +class PostSection extends Model { use HasFactory; diff --git a/tests/app/Models/SoftDeletingPost.php b/tests/app/Models/SoftDeletingPost.php index 6c9f3ab..f789131 100644 --- a/tests/app/Models/SoftDeletingPost.php +++ b/tests/app/Models/SoftDeletingPost.php @@ -2,11 +2,12 @@ namespace Oddvalue\LaravelDrafts\Tests\app\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Oddvalue\LaravelDrafts\Database\Factories\SoftDeletingPostFactory; /** - * @use \Illuminate\Database\Eloquent\Factories\HasFactory + * @use HasFactory */ class SoftDeletingPost extends Post { diff --git a/tests/app/Models/Tag.php b/tests/app/Models/Tag.php index 3077eb6..c47cd91 100644 --- a/tests/app/Models/Tag.php +++ b/tests/app/Models/Tag.php @@ -3,20 +3,21 @@ namespace Oddvalue\LaravelDrafts\Tests\app\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphTo; use Oddvalue\LaravelDrafts\Database\Factories\TagFactory; /** * @use HasFactory */ -class Tag extends \Illuminate\Database\Eloquent\Model +class Tag extends Model { use HasFactory; protected $guarded = []; /** - * @return MorphTo<\Illuminate\Database\Eloquent\Model, $this> + * @return MorphTo */ public function taggables(): MorphTo { diff --git a/tests/app/Models/User.php b/tests/app/Models/User.php index 631876f..b91c803 100644 --- a/tests/app/Models/User.php +++ b/tests/app/Models/User.php @@ -5,11 +5,13 @@ use Illuminate\Auth\Authenticatable; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\Access\Authorizable; class User extends Model implements AuthorizableContract, AuthenticatableContract { + use HasFactory; use Authorizable; use Authenticatable;