Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions src/Generators/ModelGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,18 @@ protected function hasRelations(): bool

protected function getNewModelContent(): string
{
$relations = $this->prepareRelations();

return $this->getStub('model', [
'entity' => $this->model,
'fields' => Arr::collapse($this->fields),
'relations' => $this->prepareRelations(),
'relations' => $relations,
'casts' => $this->getCasts($this->fields),
'namespace' => $this->getNamespace('models', $this->modelSubFolder),
'importRelations' => $this->getImportedRelations(),
'anotationProperties' => $this->generateAnnotationProperties($this->fields),
'annotationProperties' => $this->generateAnnotationProperties($this->fields, $relations),
'hasCarbonField' => !empty($this->fields['timestamp']) || !empty($this->fields['timestamp-required']),
'hasCollectionType' => !empty($this->relations->hasMany) || !empty($this->relations->belongsToMany),
]);
}

Expand Down Expand Up @@ -81,14 +84,19 @@ public function prepareRelatedModels(): void
$this->insertImport($content, $namespace);
}

$relationName = $this->getRelationName($this->model, $types[$type]);

$newRelation = $this->getStub('relation', [
'name' => $this->getRelationName($this->model, $types[$type]),
'name' => $relationName,
'type' => $types[$type],
'entity' => $this->model,
]);

// TODO: use ronasit/larabuilder instead
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the extra spaces before the TODO comment. Use consistent indentation with // followed by a single space.

Suggested change
// TODO: use ronasit/larabuilder instead
// TODO: use ronasit/larabuilder instead

Copilot uses AI. Check for mistakes.
$fixedContent = preg_replace('/\}$/', "\n {$newRelation}\n}", $content);

$this->insertPropertyAnnotation($fixedContent, $this->getRelationType($this->model, $types[$type]), $relationName);

$this->saveClass('models', $relation, $fixedContent);
}
}
Expand All @@ -106,6 +114,7 @@ protected function insertImport(string &$classContent, string $import): void
$import = "use {$import};";

if (!Str::contains($classContent, $import)) {
// TODO: use ronasit/larabuilder instead
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the extra spaces before the TODO comment. Use consistent indentation with // followed by a single space.

Suggested change
// TODO: use ronasit/larabuilder instead
// TODO: use ronasit/larabuilder instead

Copilot uses AI. Check for mistakes.
$classContent = preg_replace('/(namespace\s+[^;]+;\s*)/', "$1{$import}\n", $classContent, 1);
}
}
Expand Down Expand Up @@ -195,7 +204,7 @@ protected function generateClassNamespace(string $className, ?string $folder = n
return "{$path}\\{$psrPath}";
}

protected function generateAnnotationProperties(array $fields): array
protected function generateAnnotationProperties(array $fields, array $relations): array
{
$result = [];

Expand All @@ -205,6 +214,10 @@ protected function generateAnnotationProperties(array $fields): array
}
}

foreach ($relations as $relation) {
$result[$relation['name']] = $this->getRelationType($relation['entity'], $relation['type']);
}

return $result;
}

Expand Down Expand Up @@ -244,4 +257,29 @@ protected function isRequired(string $typeName): bool
{
return Str::endsWith($typeName, 'required');
}

protected function getRelationType(string $model, string $relation): string
{
if (in_array($relation, self::PLURAL_NUMBER_REQUIRED)) {
return "Collection<int, $model>";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return "Collection<int, $model>";
return "Collection<{$model}>";

}

return "{$model}|null";
}

protected function insertPropertyAnnotation(string &$content, string $propertyDataType, string $propertyName): void
{
$annotation = "* @property {$propertyDataType} \${$propertyName}";

// TODO: use ronasit/larabuilder instead
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the extra spaces before the TODO comment. Use consistent indentation with // followed by a single space.

Suggested change
// TODO: use ronasit/larabuilder instead
// TODO: use ronasit/larabuilder instead

Copilot uses AI. Check for mistakes.
if (!Str::contains($content, '/**')) {
$content = preg_replace('/^\s*class.*\n/m', "\n/**\n {$annotation}\n */$0", $content);
} else {
$content = preg_replace('/\*\/\n/', "{$annotation}\n $0", $content);
}

if (Str::contains($propertyDataType, 'Collection')) {
$this->insertImport($content, 'Illuminate\Database\Eloquent\Collection');
}
}
}
9 changes: 6 additions & 3 deletions stubs/model.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
@if($hasCarbonField)
use Carbon\Carbon;
@endif
@if($hasCollectionType)
use Illuminate\Database\Eloquent\Collection;
@endif

@if(!empty($anotationProperties))
@if(!empty($annotationProperties))
/**
@foreach($anotationProperties as $key => $value)
* @property {{ $value }} ${{ $key }}
@foreach($annotationProperties as $key => $value)
* @property {!! $value !!} ${{ $key }}
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using unescaped output {!! !!} for the property value could be a security concern if the value contains malicious content. However, since this is a code generator and values come from controlled sources (the generator itself), this is acceptable. Consider adding a comment explaining why unescaped output is necessary here (to preserve type syntax like Collection<Model>).

Copilot uses AI. Check for mistakes.
@endforeach
*/
@else
Expand Down
26 changes: 25 additions & 1 deletion tests/ModelGeneratorTest.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ className: ResourceAlreadyExistsException::class,

public function testRelationModelMissing()
{
$this->mockFileSystemWithoutCommentModel();
$this->mockFilesystem([
'User.php' => file_get_contents(getcwd() . '/tests/Support/Models/WelcomeBonus.php'),
]);

$this->assertExceptionThrew(
className: ClassNotExistsException::class,
Expand Down Expand Up @@ -244,4 +246,26 @@ className: WarningEvent::class,
message: 'Generation of model has been skipped cause the view incorrect_stub from the config entity-generator.stubs.relation is not exists. Please check that config has the correct view name value.',
);
}

public function testAddPropertyAnnotationToRelatedModel()
{
$this->mockFilesystem([
'Post.php' => $this->getFixture('new_model_without_fields.php'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may we just add the Post model to the mockFilesystem method?

]);

app(ModelGenerator::class)
->setModel('Category')
->setFields([])
->setRelations(new RelationsDTO(
belongsToMany: ['Post'],
))
->generate();

$this->assertGeneratedFileEquals('related_model_with_property.php', 'app/Models/Post.php');

$this->assertEventPushed(
className: SuccessCreateMessage::class,
message: 'Created a new Model: Category',
);
}
}
28 changes: 10 additions & 18 deletions tests/Support/Model/ModelMockTrait.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,19 @@ trait ModelMockTrait
{
use GeneratorMockTrait;


public function mockFileSystemWithoutCommentModel(): void
{
$fileSystemMock = new FileSystemMock();

$fileSystemMock->models = [
'User.php' => file_get_contents(getcwd() . '/tests/Support/Models/WelcomeBonus.php'),
];

$fileSystemMock->setStructure();
}

public function mockFilesystem(): void
public function mockFilesystem(array $models = []): void
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's implement it as separate methods

  1. public function mockFilesystem(array $models = []): void
  2. public function mockDefaultFilesystem(): void

Copy link
Contributor

@AZabolotnikov AZabolotnikov Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@neellii @DenTray

public function mockFilesystem(array $models = []): void

Are you sure about this?

MB: public function mockFilesystem(array $models): void

{
$fileSystemMock = new FileSystemMock;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$fileSystemMock = new FileSystemMock;
$fileSystemMock = new FileSystemMock();

ref: https://www.php-fig.org/psr/psr-12/#4-classes-properties-and-methods


$fileSystemMock->models = [
'Comment.php' => file_get_contents(getcwd() . '/tests/Support/Models/WelcomeBonus.php'),
'User.php' => file_get_contents(getcwd() . '/tests/Support/Models/WelcomeBonus.php'),
'Forum/Author.php' => file_get_contents(getcwd() . '/tests/Support/Models/WelcomeBonus.php'),
];
if (!empty ($models)) {
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the space between !empty and the opening parenthesis. The correct syntax is !empty($models).

Suggested change
if (!empty ($models)) {
if (!empty($models)) {

Copilot uses AI. Check for mistakes.
$fileSystemMock->models = $models;
} else {
$fileSystemMock->models = [
'Comment.php' => file_get_contents(getcwd() . '/tests/Support/Models/WelcomeBonus.php'),
'User.php' => file_get_contents(getcwd() . '/tests/Support/Models/WelcomeBonus.php'),
'Forum/Author.php' => file_get_contents(getcwd() . '/tests/Support/Models/WelcomeBonus.php'),
];
}

$fileSystemMock->setStructure();
}
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/ModelGeneratorTest/comment_relation_model.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
use Illuminate\Database\Eloquent\Model;
use RonasIT\Support\Traits\ModelTrait;

/**
* @property Post|null $post
*/
class WelcomeBonus extends Model
{
use ModelTrait;
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/ModelGeneratorTest/new_model.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Database\Eloquent\Model;
use RonasIT\Support\Traits\ModelTrait;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;

/**
* @property int|null $priority
Expand All @@ -20,6 +21,8 @@
* @property Carbon|null $updated_at
* @property Carbon $published_at
* @property array $meta
* @property Comment|null $comment
* @property Collection<int, User> $users
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @property Collection<int, User> $users
* @property Collection<User> $users

*/
class Post extends Model
{
Expand Down
3 changes: 2 additions & 1 deletion tests/fixtures/ModelGeneratorTest/new_model_with_many_relations.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
use Illuminate\Database\Eloquent\Model;
use RonasIT\Support\Traits\ModelTrait;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;

//TODO: add @property annotation for each model's field
/**
* @property Collection<int, User> $users
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @property Collection<int, User> $users
* @property Collection<User> $users

*/
class Post extends Model
{
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/ModelGeneratorTest/new_model_with_subfolers_relations.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
use Illuminate\Database\Eloquent\Model;
use RonasIT\Support\Traits\ModelTrait;
use App\Models\Forum\Author;
use Illuminate\Database\Eloquent\Collection;

/**
* @property string $title
* @property Collection<int, Author> $authors
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @property Collection<int, Author> $authors
* @property Collection<Author> $authors

*/
class Post extends Model
{
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/ModelGeneratorTest/new_subfolders_model.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Models\Comment;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;

/**
* @property int|null $priority
Expand All @@ -22,6 +23,8 @@
* @property Carbon|null $updated_at
* @property Carbon $published_at
* @property array $meta
* @property Comment|null $comment
* @property Collection<int, User> $users
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @property Collection<int, User> $users
* @property Collection<User> $users

*/
class Post extends Model
{
Expand Down
26 changes: 26 additions & 0 deletions tests/fixtures/ModelGeneratorTest/related_model_with_property.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use RonasIT\Support\Traits\ModelTrait;

//TODO: add @property annotation for each model's field
/**
* @property Collection<int, Category> $categories
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @property Collection<int, Category> $categories
* @property Collection<Category> $categories

*/
class Post extends Model
{
use ModelTrait;

protected $fillable = [
];

protected $hidden = ['pivot'];

public function categories()
{
return $this->belongsToMany(Category::class);
}
}