diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 22d47fec..6fb65532 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -462,6 +462,24 @@ parameters: count: 1 path: src/Support/SelectFields.php + - + rawMessage: 'Method Rebing\GraphQL\Support\SelectFields::handleMorphToRelation() has parameter $field with no value type specified in iterable type array.' + identifier: missingType.iterableValue + count: 1 + path: src/Support/SelectFields.php + + - + rawMessage: 'Method Rebing\GraphQL\Support\SelectFields::handleMorphToRelation() has parameter $queryArgs with no value type specified in iterable type array.' + identifier: missingType.iterableValue + count: 1 + path: src/Support/SelectFields.php + + - + rawMessage: 'Method Rebing\GraphQL\Support\SelectFields::handleMorphToRelation() has parameter $with with no value type specified in iterable type array.' + identifier: missingType.iterableValue + count: 1 + path: src/Support/SelectFields.php + - rawMessage: 'Method Rebing\GraphQL\Support\SelectFields::handleRelation() has parameter $field with no value type specified in iterable type array.' identifier: missingType.iterableValue @@ -1032,6 +1050,36 @@ parameters: count: 1 path: tests/Database/SelectFields/ValidateFieldTests/ValidateFieldsQuery.php + - + rawMessage: 'Method Rebing\GraphQL\Tests\Database\SelectFields\UnionMorphTests\CommentsQuery::resolve() has parameter $args with no type specified.' + identifier: missingType.parameter + count: 1 + path: tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php + + - + rawMessage: 'Method Rebing\GraphQL\Tests\Database\SelectFields\UnionMorphTests\CommentsQuery::resolve() has parameter $context with no type specified.' + identifier: missingType.parameter + count: 1 + path: tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php + + - + rawMessage: 'Method Rebing\GraphQL\Tests\Database\SelectFields\UnionMorphTests\CommentsQuery::resolve() has parameter $root with no type specified.' + identifier: missingType.parameter + count: 1 + path: tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php + + - + rawMessage: 'Method Rebing\GraphQL\Tests\Database\SelectFields\UnionMorphTests\CommentsQuery::resolve() return type has no value type specified in iterable type array.' + identifier: missingType.iterableValue + count: 1 + path: tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php + + - + rawMessage: 'Method Rebing\GraphQL\Tests\Database\SelectFields\UnionMorphTests\CommentsQuery::resolve() return type has no value type specified in iterable type array|Illuminate\Database\Eloquent\Collection.' + identifier: missingType.iterableValue + count: 1 + path: tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php + - rawMessage: 'Method Rebing\GraphQL\Tests\Support\Objects\CustomExamplesQuery::resolve() has no return type specified.' identifier: missingType.return diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index 661cb647..a4b20b06 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -193,7 +193,19 @@ protected static function handleFields( } // With - elseif (\is_array($field['fields']) && !empty($field['fields']) && $queryable) { + elseif (is_a($parentTypeUnwrapped, \GraphQL\Type\Definition\InterfaceType::class)) { + static::handleInterfaceFields( + $queryArgs, + $field, + $parentTypeUnwrapped, + $select, + $with, + $ctx, + $fieldObject, + $key, + $customQuery + ); + } elseif (\is_array($field['fields']) && !empty($field['fields']) && $queryable) { if (isset($parentType->config['model'])) { // Get the next parent type, so that 'with' queries could be made // Both keys for the relation are required (e.g 'id' <-> 'user_id') @@ -207,26 +219,29 @@ protected static function handleFields( static::addAlwaysFields($fieldObject, $field, $parentTable, true); - $with[$relationsKey] = static::getSelectableFieldsAndRelations( - $queryArgs, - $field, - $newParentType, - $customQuery, - false, - $ctx - ); - } elseif (is_a($parentTypeUnwrapped, \GraphQL\Type\Definition\InterfaceType::class)) { - static::handleInterfaceFields( - $queryArgs, - $field, - $parentTypeUnwrapped, - $select, - $with, - $ctx, - $fieldObject, - $key, - $customQuery - ); + // Check if this is a MorphTo relation + if (is_a($relation, MorphTo::class)) { + // For MorphTo relations, we need to handle them specially + // because they can have different models, and we need to eager load based on the query + static::handleMorphToRelation( + $queryArgs, + $field, + $with, + $ctx, + $fieldObject, + $key, + $customQuery + ); + } else { + $with[$relationsKey] = static::getSelectableFieldsAndRelations( + $queryArgs, + $field, + $newParentType, + $customQuery, + false, + $ctx + ); + } } else { static::handleFields($queryArgs, $field, $fieldObject->config['type'], $select, $with, $ctx); } @@ -265,6 +280,31 @@ protected static function isMongodbInstance(GraphqlType $parentType): bool return isset($parentType->config['model']) ? app($parentType->config['model']) instanceof $mongoType : false; } + /** + * Get types from UnionType or InterfaceType. + * + * @return GraphqlType[] + */ + protected static function getTypesFromUnionOrInterface(GraphqlType $type): array + { + if ($type instanceof UnionType) { + return $type->getTypes(); + } + + if ($type instanceof \GraphQL\Type\Definition\InterfaceType) { + // For InterfaceType, get types from the config + // @phpstan-ignore-next-line - InterfaceType can have custom 'types' config + if (isset($type->config['types']) && \is_callable($type->config['types'])) { + /** @var callable(): array $typesCallable */ + $typesCallable = $type->config['types']; + + return $typesCallable(); + } + } + + return []; + } + /** * @param string|Expression $field * @param array $select Passed by reference, adds further fields to select @@ -451,6 +491,23 @@ protected static function handleInterfaceFields( ): void { $relationsKey = Arr::get($fieldObject->config, 'alias', $key); + // Check if this field is actually a relation + $queryable = static::isQueryable($fieldObject->config); + $isRelation = $queryable && \is_array($field['fields']) && !empty($field['fields']); + + // If it's not a relation, handle it as a regular field + if (!$isRelation) { + // @phpstan-ignore-next-line - Custom config key + $key = $fieldObject->config['alias'] ?? $key; + // @phpstan-ignore-next-line - alias can be Closure or string + $key = $key instanceof Closure ? $key() : $key; + $parentTable = static::isMongodbInstance($parentType) ? null : static::getTableNameFromParentType($parentType); + static::addFieldToSelect($key, $select, $parentTable, false); + static::addAlwaysFields($fieldObject, $select, $parentTable); + + return; + } + $with[$relationsKey] = function ($query) use ( $queryArgs, $field, @@ -461,6 +518,11 @@ protected static function handleInterfaceFields( $key, $fieldObject ) { + // Check if $query is actually a relation + if (!($query instanceof Relation)) { + return $query; + } + $parentTable = static::isMongodbInstance($parentType) ? null : static::getTableNameFromParentType($parentType); static::handleRelation($select, $query, $parentTable, $field); @@ -518,6 +580,93 @@ function (GraphqlType $type) use ($query) { }; } + /** + * Handle MorphTo relations + * @param mixed $ctx + */ + protected static function handleMorphToRelation( + array $queryArgs, + array $field, + array &$with, + $ctx, + FieldDefinition $fieldObject, + string $key, + ?Closure $customQuery + ): void { + $relationsKey = Arr::get($fieldObject->config, 'alias', $key); + + /* @var GraphqlType $fieldType */ + $fieldType = $fieldObject->config['type']; + + if ($fieldType instanceof WrappingType) { + $fieldType = $fieldType->getInnermostType(); + } + + $relationNames = (isset($fieldType->config['relationName']) && \is_callable($fieldType->config['relationName'])) + ? $fieldType->config['relationName']() + : null; + + // @phpstan-ignore-next-line - getInnermostType returns Type which is GraphqlType + $types = static::getTypesFromUnionOrInterface($fieldType); + $isInterface = $fieldType instanceof \GraphQL\Type\Definition\InterfaceType; + + $with[$relationsKey] = function ($relation) use ($queryArgs, $field, $types, $relationNames, $customQuery, $ctx, $isInterface) { + // Check if $relation is actually a MorphTo relation + if (!($relation instanceof MorphTo)) { + return $relation; + } + + $morphRelation = []; + + foreach ($types as $type) { + // Get the model class name for the morph type + if (isset($type->config['model'])) { + $modelClass = $type->config['model']; + } else { + // Fallback to type name if no model is configured + $typeName = $type instanceof \GraphQL\Type\Definition\NamedType ? $type->name() : ''; + $modelClass = $relationNames[$typeName] ?? $typeName; + } + + /** @var callable $callable */ + $callable = static::getSelectableFieldsAndRelations( + $queryArgs, + $field, + $type, + $customQuery, + false, + $ctx + ); + + // If the field type is an interface, wrap the callable to select * instead + // This is necessary because interfaces can have different implementations + // with different fields, so we need to select all fields + if ($isInterface) { + $originalCallable = $callable; + $callable = function ($query) use ($originalCallable) { + // Call the original callable first to set up relations + $originalCallable($query); + // Override select to use * for interface types + // We need to clear existing columns and set to * to ensure + // all fields from the concrete type are selected + $query->getQuery()->columns = null; + $query->select('*'); + + return $query; + }; + } + + $morphRelation[$modelClass] = $callable; + } + + if (!empty($morphRelation)) { + $relation->constrain($morphRelation); + } + + return $relation; + }; + } + public function getSelect(): array { return $this->select; diff --git a/src/Support/UnionType.php b/src/Support/UnionType.php index 7f13fe8b..955c1ab8 100644 --- a/src/Support/UnionType.php +++ b/src/Support/UnionType.php @@ -30,6 +30,10 @@ public function getAttributes(): array $attributes['resolveType'] = [$this, 'resolveType']; } + if (method_exists($this, 'relationName')) { + $attributes['relationName'] = [$this, 'relationName']; + } + return $attributes; } diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentType.php b/tests/Database/SelectFields/UnionMorphTests/CommentType.php new file mode 100644 index 00000000..80f96698 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/CommentType.php @@ -0,0 +1,38 @@ + 'Comment', + 'model' => Comment::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'title' => [ + 'type' => Type::nonNull(Type::string()), + ], + 'body' => [ + 'type' => Type::string(), + ], + 'file' => [ + 'type' => GraphQL::type('File'), + ], + 'commentable' => [ + 'type' => GraphQL::type('CommentableUnion'), + ], + ]; + } +} diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php b/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php new file mode 100644 index 00000000..0858c6dc --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php @@ -0,0 +1,49 @@ + 'CommentableUnion', + ]; + + public function types(): array + { + return [ + GraphQL::type('Post'), + GraphQL::type('Product'), + ]; + } + + /** + * @return array + */ + public function relationName(): array + { + return [ + Post::class => 'post', + Product::class => 'product', + ]; + } + + public function resolveType(object $value): ?GraphqlType + { + if ($value instanceof Post) { + return GraphQL::type('Post'); + } + + if ($value instanceof Product) { + return GraphQL::type('Product'); + } + + return null; + } +} diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php b/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php new file mode 100644 index 00000000..1c4bfe85 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php @@ -0,0 +1,35 @@ + 'comments', + ]; + + public function type(): Type + { + return Type::listOf(GraphQL::type('Comment')); + } + + public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields): array|\Illuminate\Database\Eloquent\Collection + { + /** @var SelectFields $selectFields */ + $selectFields = $getSelectFields(); + + return Comment::query() + ->select($selectFields->getSelect()) + ->with($selectFields->getRelations()) + ->get(); + } +} diff --git a/tests/Database/SelectFields/UnionMorphTests/FileType.php b/tests/Database/SelectFields/UnionMorphTests/FileType.php new file mode 100644 index 00000000..0cab9b86 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/FileType.php @@ -0,0 +1,35 @@ + 'File', + 'model' => File::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'name' => [ + 'type' => Type::string(), + ], + 'path' => [ + 'type' => Type::string(), + ], + 'folder' => [ + 'type' => GraphQL::type('Folder'), + ], + ]; + } +} diff --git a/tests/Database/SelectFields/UnionMorphTests/FolderType.php b/tests/Database/SelectFields/UnionMorphTests/FolderType.php new file mode 100644 index 00000000..aefdd7f3 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/FolderType.php @@ -0,0 +1,28 @@ + 'Folder', + 'model' => Folder::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'name' => [ + 'type' => Type::string(), + ], + ]; + } +} diff --git a/tests/Database/SelectFields/UnionMorphTests/PostType.php b/tests/Database/SelectFields/UnionMorphTests/PostType.php new file mode 100644 index 00000000..57df053d --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/PostType.php @@ -0,0 +1,32 @@ + 'Post', + 'model' => Post::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'title' => [ + 'type' => Type::string(), + ], + 'file' => [ + 'type' => GraphQL::type('File'), + ], + ]; + } +} diff --git a/tests/Database/SelectFields/UnionMorphTests/ProductType.php b/tests/Database/SelectFields/UnionMorphTests/ProductType.php new file mode 100644 index 00000000..45aacdb6 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/ProductType.php @@ -0,0 +1,35 @@ + 'Product', + 'model' => Product::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'name' => [ + 'type' => Type::string(), + ], + 'price' => [ + 'type' => Type::int(), + ], + 'file' => [ + 'type' => GraphQL::type('File'), + ], + ]; + } +} diff --git a/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php b/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php new file mode 100644 index 00000000..1f154ed4 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php @@ -0,0 +1,236 @@ +set('graphql.schemas.default', [ + 'query' => [ + CommentsQuery::class, + ], + ]); + + $app['config']->set('graphql.types', [ + FileType::class, + FolderType::class, + PostType::class, + ProductType::class, + CommentableUnionType::class, + CommentType::class, + ]); + + Model::preventLazyLoading(); + } + + public function testUnionMorphEagerLoading(): void + { + // Create 2 products with different files and product folder + /** @var Folder $productFolder */ + $productFolder = Folder::factory()->create(['name' => 'product']); + + /** @var File $productFile1 */ + $productFile1 = File::factory()->create(['folder_id' => $productFolder->id, 'name' => 'product_file_1.pdf']); + + /** @var File $productFile2 */ + $productFile2 = File::factory()->create(['folder_id' => $productFolder->id, 'name' => 'product_file_2.jpg']); + + /** @var Product $product1 */ + $product1 = Product::factory()->create(['file_id' => $productFile1->id, 'name' => 'Product 1']); + + /** @var Product $product2 */ + $product2 = Product::factory()->create(['file_id' => $productFile2->id, 'name' => 'Product 2']); + + // Create 2 posts with different files and post folder + /** @var User $user */ + $user = User::factory()->create(); + + /** @var Folder $postFolder */ + $postFolder = Folder::factory()->create(['name' => 'post']); + + /** @var File $postFile1 */ + $postFile1 = File::factory()->create(['folder_id' => $postFolder->id, 'name' => 'post_file_1.docx']); + + /** @var File $postFile2 */ + $postFile2 = File::factory()->create(['folder_id' => $postFolder->id, 'name' => 'post_file_2.png']); + + /** @var Post $post1 */ + $post1 = Post::factory()->create(['user_id' => $user->id, 'file_id' => $postFile1->id, 'title' => 'Post 1']); + + /** @var Post $post2 */ + $post2 = Post::factory()->create(['user_id' => $user->id, 'file_id' => $postFile2->id, 'title' => 'Post 2']); + + // Create comments for all products and posts using morphable relationships + /** @var Comment $product1Comment */ + $product1Comment = Comment::factory()->create(); + $product1->commentableComments()->save($product1Comment); + + /** @var Comment $product2Comment */ + $product2Comment = Comment::factory()->create(); + $product2->commentableComments()->save($product2Comment); + + /** @var Comment $post1Comment */ + $post1Comment = Comment::factory()->create(); + $post1->commentableComments()->save($post1Comment); + + /** @var Comment $post2Comment */ + $post2Comment = Comment::factory()->create(); + $post2->commentableComments()->save($post2Comment); + + $query = +/** @lang GraphQL */ <<<'GRAQPHQL' +{ + comments { + id + title + body + commentable { + ... on Post { + id + title + file { + id + name + path + folder { + id + name + } + } + } + ... on Product { + id + name + price + file { + id + name + path + folder { + id + name + } + } + } + } + } +} +GRAQPHQL; + + $this->sqlCounterReset(); + + $result = $this->httpGraphql($query); + + $this->assertSqlQueries( + /** @lang SQL */ + <<<'SQL' +select "comments"."id", "comments"."title", "comments"."body", "comments"."commentable_id", "comments"."commentable_type" from "comments"; +select "products"."id", "products"."name", "products"."price", "products"."file_id" from "products" where "products"."id" in (?, ?); +select "files"."id", "files"."name", "files"."path", "files"."folder_id" from "files" where "files"."id" in (?, ?); +select "folders"."id", "folders"."name" from "folders" where "folders"."id" in (?); +select "posts"."id", "posts"."file_id", "posts"."title" from "posts" where "posts"."id" in (?, ?); +select "files"."id", "files"."name", "files"."path", "files"."folder_id" from "files" where "files"."id" in (?, ?); +select "folders"."id", "folders"."name" from "folders" where "folders"."id" in (?); +SQL + ); + + $expectedResult = [ + 'data' => [ + 'comments' => [ + [ + 'id' => (string) $product1Comment->id, + 'title' => $product1Comment->title, + 'body' => $product1Comment->body, + 'commentable' => [ + 'id' => (string) $product1->id, + 'name' => $product1->name, + 'price' => $product1->price, + 'file' => [ + 'id' => (string) $productFile1->id, + 'name' => $productFile1->name, + 'path' => $productFile1->path, + 'folder' => [ + 'id' => (string) $productFolder->id, + 'name' => $productFolder->name, + ], + ], + ], + ], + [ + 'id' => (string) $product2Comment->id, + 'title' => $product2Comment->title, + 'body' => $product2Comment->body, + 'commentable' => [ + 'id' => (string) $product2->id, + 'name' => $product2->name, + 'price' => $product2->price, + 'file' => [ + 'id' => (string) $productFile2->id, + 'name' => $productFile2->name, + 'path' => $productFile2->path, + 'folder' => [ + 'id' => (string) $productFolder->id, + 'name' => $productFolder->name, + ], + ], + ], + ], + [ + 'id' => (string) $post1Comment->id, + 'title' => $post1Comment->title, + 'body' => $post1Comment->body, + 'commentable' => [ + 'id' => (string) $post1->id, + 'title' => $post1->title, + 'file' => [ + 'id' => (string) $postFile1->id, + 'name' => $postFile1->name, + 'path' => $postFile1->path, + 'folder' => [ + 'id' => (string) $postFolder->id, + 'name' => $postFolder->name, + ], + ], + ], + ], + [ + 'id' => (string) $post2Comment->id, + 'title' => $post2Comment->title, + 'body' => $post2Comment->body, + 'commentable' => [ + 'id' => (string) $post2->id, + 'title' => $post2->title, + 'file' => [ + 'id' => (string) $postFile2->id, + 'name' => $postFile2->name, + 'path' => $postFile2->path, + 'folder' => [ + 'id' => (string) $postFolder->id, + 'name' => $postFolder->name, + ], + ], + ], + ], + ], + ], + ]; + + self::assertSame($expectedResult, $result); + } +} diff --git a/tests/Support/Models/Comment.php b/tests/Support/Models/Comment.php index ef66191e..ace15198 100644 --- a/tests/Support/Models/Comment.php +++ b/tests/Support/Models/Comment.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Relations\MorphTo; use Rebing\GraphQL\Tests\Support\database\factories\CommentFactory; /** @@ -17,7 +18,7 @@ * @property string $title * @property string|null $body * @property bool $flag - * @property-read Post $post + * @property-read Post $post * @property-read Collection|Like[] $likes */ class Comment extends Model @@ -34,6 +35,11 @@ public function likes(): MorphMany return $this->morphMany(Like::class, 'likable'); } + public function commentable(): MorphTo + { + return $this->morphTo(); + } + protected static function newFactory(): Factory { return CommentFactory::new(); diff --git a/tests/Support/Models/File.php b/tests/Support/Models/File.php new file mode 100644 index 00000000..3642ed5f --- /dev/null +++ b/tests/Support/Models/File.php @@ -0,0 +1,36 @@ +belongsTo(Folder::class); + } +} diff --git a/tests/Support/Models/Folder.php b/tests/Support/Models/Folder.php new file mode 100644 index 00000000..37a00e75 --- /dev/null +++ b/tests/Support/Models/Folder.php @@ -0,0 +1,32 @@ +hasMany(File::class); + } + + protected static function newFactory(): Factory + { + return FolderFactory::new(); + } +} diff --git a/tests/Support/Models/Post.php b/tests/Support/Models/Post.php index 538823ba..f5e77b62 100644 --- a/tests/Support/Models/Post.php +++ b/tests/Support/Models/Post.php @@ -24,6 +24,7 @@ * @property bool $is_published * @property-read Collection|Comment[] $comments * @property-read Collection|Like[] $likes + * @property-read Collection|File[] $files */ class Post extends Model { @@ -49,6 +50,16 @@ public function comments(): HasMany return $this->hasMany(Comment::class)->orderBy('comments.id'); } + public function commentableComments(): MorphMany + { + return $this->morphMany(Comment::class, 'commentable'); + } + + public function file(): BelongsTo + { + return $this->belongsTo(File::class, 'file_id'); + } + public function likes(): MorphMany { return $this->morphMany(Like::class, 'likable'); diff --git a/tests/Support/Models/Product.php b/tests/Support/Models/Product.php new file mode 100644 index 00000000..9bdb52ec --- /dev/null +++ b/tests/Support/Models/Product.php @@ -0,0 +1,52 @@ +belongsTo(File::class, 'file_id'); + } + + public function commentableComments(): MorphMany + { + return $this->morphMany(Comment::class, 'commentable'); + } + + public function getIsPublishedAttribute(): bool + { + $publishedAt = $this->published_at; + + return null !== $publishedAt; + } + + protected static function newFactory(): Factory + { + return ProductFactory::new(); + } +} diff --git a/tests/Support/database/factories/FileFactory.php b/tests/Support/database/factories/FileFactory.php new file mode 100644 index 00000000..86a15851 --- /dev/null +++ b/tests/Support/database/factories/FileFactory.php @@ -0,0 +1,21 @@ + $this->faker->word() . '.txt', + 'path' => '/files/' . $this->faker->word(), + 'folder_id' => null, + ]; + } +} diff --git a/tests/Support/database/factories/FolderFactory.php b/tests/Support/database/factories/FolderFactory.php new file mode 100644 index 00000000..240c5488 --- /dev/null +++ b/tests/Support/database/factories/FolderFactory.php @@ -0,0 +1,19 @@ + $this->faker->word(), + ]; + } +} diff --git a/tests/Support/database/factories/ProductFactory.php b/tests/Support/database/factories/ProductFactory.php new file mode 100644 index 00000000..91261266 --- /dev/null +++ b/tests/Support/database/factories/ProductFactory.php @@ -0,0 +1,23 @@ + + */ +class ProductFactory extends Factory +{ + protected $model = Product::class; + + public function definition(): array + { + return [ + 'name' => fake()->words(3, true), + 'price' => fake()->numberBetween(100, 1_000_000), + ]; + } +} diff --git a/tests/Support/database/migrations/____comments_table.php b/tests/Support/database/migrations/____comments_table.php index 959f35dc..b1b65c9c 100644 --- a/tests/Support/database/migrations/____comments_table.php +++ b/tests/Support/database/migrations/____comments_table.php @@ -12,7 +12,8 @@ public function up(): void { Schema::create('comments', function (Blueprint $table): void { $table->increments('id'); - $table->integer('post_id'); + $table->integer('post_id')->nullable(); + $table->nullableMorphs('commentable'); $table->string('title'); $table->string('body')->nullable(); $table->boolean('flag')->default('false'); diff --git a/tests/Support/database/migrations/____files_table.php b/tests/Support/database/migrations/____files_table.php new file mode 100644 index 00000000..84fa78e5 --- /dev/null +++ b/tests/Support/database/migrations/____files_table.php @@ -0,0 +1,22 @@ +increments('id'); + $table->unsignedInteger('folder_id')->nullable(); + $table->string('name'); + $table->string('path'); + $table->unsignedInteger('size')->default(0); + $table->timestamps(); + }); + } +} diff --git a/tests/Support/database/migrations/____folders_table.php b/tests/Support/database/migrations/____folders_table.php new file mode 100644 index 00000000..880c8d5e --- /dev/null +++ b/tests/Support/database/migrations/____folders_table.php @@ -0,0 +1,20 @@ +increments('id'); + $table->string(column: 'name'); + $table->unsignedInteger('parent_id')->nullable(); + $table->timestamps(); + }); + } +} diff --git a/tests/Support/database/migrations/____posts_table.php b/tests/Support/database/migrations/____posts_table.php index 8c5ad33d..9a57210c 100644 --- a/tests/Support/database/migrations/____posts_table.php +++ b/tests/Support/database/migrations/____posts_table.php @@ -17,6 +17,7 @@ public function up(): void $table->integer('user_id')->nullable(); $table->text('properties')->nullable(); $table->boolean('flag')->default('false'); + $table->unsignedInteger('file_id')->nullable(); $table->dateTime('published_at')->nullable(); $table->timestamps(); }); diff --git a/tests/Support/database/migrations/____products_table.php b/tests/Support/database/migrations/____products_table.php new file mode 100644 index 00000000..3a846328 --- /dev/null +++ b/tests/Support/database/migrations/____products_table.php @@ -0,0 +1,22 @@ +increments('id'); + $table->string('name'); + $table->decimal('price', 10, 2)->nullable(); + $table->unsignedInteger('file_id')->nullable(); + $table->dateTime(column: 'published_at')->nullable(); + $table->timestamps(); + }); + } +}