mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 12:11:14 -05:00
- Added rides index view with search and filter options. - Created rides show view to display ride details. - Implemented API routes for rides. - Developed authentication routes for user registration, login, and email verification. - Created tests for authentication, email verification, password reset, and user profile management. - Added feature tests for rides and operators, including creation, updating, deletion, and searching. - Implemented soft deletes and caching for rides and operators. - Enhanced manufacturer and operator model tests for various functionalities.
857 lines
23 KiB
PHP
857 lines
23 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
class MakeThrillWikiModel extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'make:thrillwiki-model {name : The name of the model}
|
|
{--migration : Generate a migration file}
|
|
{--factory : Generate a model factory}
|
|
{--with-relationships : Include common ThrillWiki relationships}
|
|
{--cached : Add caching traits and methods}
|
|
{--api-resource : Generate API resource class}
|
|
{--with-tests : Generate model tests}
|
|
{--force : Overwrite existing files}';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Generate a ThrillWiki model with optimized patterns, traits, and optional related files';
|
|
|
|
/**
|
|
* ThrillWiki traits for different model types
|
|
*/
|
|
protected array $thrillWikiTraits = [
|
|
'HasLocation' => 'App\\Traits\\HasLocation',
|
|
'HasSlugHistory' => 'App\\Traits\\HasSlugHistory',
|
|
'HasStatistics' => 'App\\Traits\\HasStatistics',
|
|
'HasCaching' => 'App\\Traits\\HasCaching',
|
|
'HasSoftDeletes' => 'Illuminate\\Database\\Eloquent\\SoftDeletes',
|
|
'HasFactory' => 'Illuminate\\Database\\Eloquent\\Factories\\HasFactory',
|
|
];
|
|
|
|
/**
|
|
* Common ThrillWiki relationships by model type
|
|
*/
|
|
protected array $relationshipPatterns = [
|
|
'Park' => [
|
|
'areas' => 'hasMany:ParkArea',
|
|
'rides' => 'hasManyThrough:Ride,ParkArea',
|
|
'operator' => 'belongsTo:Operator',
|
|
'photos' => 'morphMany:Photo',
|
|
'reviews' => 'morphMany:Review',
|
|
],
|
|
'Ride' => [
|
|
'park' => 'belongsTo:Park',
|
|
'area' => 'belongsTo:ParkArea',
|
|
'manufacturer' => 'belongsTo:Manufacturer',
|
|
'designer' => 'belongsTo:Designer',
|
|
'photos' => 'morphMany:Photo',
|
|
'reviews' => 'morphMany:Review',
|
|
],
|
|
'Operator' => [
|
|
'parks' => 'hasMany:Park',
|
|
],
|
|
'Manufacturer' => [
|
|
'rides' => 'hasMany:Ride,manufacturer_id',
|
|
],
|
|
'Designer' => [
|
|
'rides' => 'hasMany:Ride,designer_id',
|
|
],
|
|
'Review' => [
|
|
'user' => 'belongsTo:User',
|
|
'reviewable' => 'morphTo',
|
|
],
|
|
];
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*/
|
|
public function handle()
|
|
{
|
|
$this->info('🚀 Generating ThrillWiki Model for: ' . $this->argument('name'));
|
|
|
|
$name = $this->argument('name');
|
|
$className = Str::studly($name);
|
|
$tableName = Str::snake(Str::plural($name));
|
|
|
|
// Generate model
|
|
$this->generateModel($className);
|
|
|
|
// Generate optional files
|
|
if ($this->option('migration')) {
|
|
$this->generateMigration($className, $tableName);
|
|
}
|
|
|
|
if ($this->option('factory')) {
|
|
$this->generateFactory($className);
|
|
}
|
|
|
|
if ($this->option('api-resource')) {
|
|
$this->generateApiResource($className);
|
|
}
|
|
|
|
if ($this->option('with-tests')) {
|
|
$this->generateTests($className);
|
|
}
|
|
|
|
$this->displaySummary($className);
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Generate the model file
|
|
*/
|
|
protected function generateModel(string $className): void
|
|
{
|
|
$modelPath = app_path("Models/{$className}.php");
|
|
|
|
if (File::exists($modelPath) && !$this->option('force')) {
|
|
$this->error("Model {$className} already exists! Use --force to overwrite.");
|
|
return;
|
|
}
|
|
|
|
$modelContent = $this->buildModelContent($className);
|
|
|
|
$this->ensureDirectoryExists(dirname($modelPath));
|
|
File::put($modelPath, $modelContent);
|
|
|
|
$this->line("✅ Model created: app/Models/{$className}.php");
|
|
}
|
|
|
|
/**
|
|
* Build the model content with ThrillWiki patterns
|
|
*/
|
|
protected function buildModelContent(string $className): string
|
|
{
|
|
$tableName = Str::snake(Str::plural($className));
|
|
$traits = $this->getTraitsForModel($className);
|
|
$relationships = $this->getRelationshipsForModel($className);
|
|
$cachingMethods = $this->option('cached') ? $this->getCachingMethods($className) : '';
|
|
|
|
$stub = <<<'PHP'
|
|
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
{TRAIT_IMPORTS}
|
|
|
|
/**
|
|
* {CLASS_NAME} Model
|
|
*
|
|
* Generated by ThrillWiki Model Generator
|
|
* Includes ThrillWiki optimization patterns and performance enhancements
|
|
*/
|
|
class {CLASS_NAME} extends Model
|
|
{
|
|
{TRAITS}
|
|
|
|
/**
|
|
* The table associated with the model.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $table = '{TABLE_NAME}';
|
|
|
|
/**
|
|
* The attributes that are mass assignable.
|
|
*
|
|
* @var array<int, string>
|
|
*/
|
|
protected $fillable = [
|
|
'name',
|
|
'description',
|
|
'is_active',
|
|
// Add more fillable attributes as needed
|
|
];
|
|
|
|
/**
|
|
* The attributes that should be cast.
|
|
*
|
|
* @var array<string, string>
|
|
*/
|
|
protected $casts = [
|
|
'is_active' => 'boolean',
|
|
'created_at' => 'datetime',
|
|
'updated_at' => 'datetime',
|
|
// Add more casts as needed
|
|
];
|
|
|
|
/**
|
|
* The attributes that should be hidden for arrays.
|
|
*
|
|
* @var array<int, string>
|
|
*/
|
|
protected $hidden = [
|
|
// Add hidden attributes if needed
|
|
];
|
|
|
|
// Query Scopes
|
|
|
|
/**
|
|
* Scope a query to only include active records.
|
|
*/
|
|
public function scopeActive($query)
|
|
{
|
|
return $query->where('is_active', true);
|
|
}
|
|
|
|
/**
|
|
* Scope for optimized queries with common relationships.
|
|
*/
|
|
public function scopeOptimized($query)
|
|
{
|
|
return $query->with($this->getOptimizedRelations());
|
|
}
|
|
|
|
// ThrillWiki Methods
|
|
|
|
/**
|
|
* Get optimized relations for this model.
|
|
*/
|
|
public function getOptimizedRelations(): array
|
|
{
|
|
return [
|
|
// Define common relationships to eager load
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get cache key for this model instance.
|
|
*/
|
|
public function getCacheKey(string $suffix = ''): string
|
|
{
|
|
$key = strtolower(class_basename($this)) . '.' . $this->id;
|
|
return $suffix ? $key . '.' . $suffix : $key;
|
|
}
|
|
|
|
{RELATIONSHIPS}
|
|
|
|
{CACHING_METHODS}
|
|
}
|
|
PHP;
|
|
|
|
return str_replace([
|
|
'{CLASS_NAME}',
|
|
'{TABLE_NAME}',
|
|
'{TRAIT_IMPORTS}',
|
|
'{TRAITS}',
|
|
'{RELATIONSHIPS}',
|
|
'{CACHING_METHODS}',
|
|
], [
|
|
$className,
|
|
$tableName,
|
|
$this->buildTraitImports($traits),
|
|
$this->buildTraitUses($traits),
|
|
$relationships,
|
|
$cachingMethods,
|
|
], $stub);
|
|
}
|
|
|
|
/**
|
|
* Get traits for the model based on options and model type
|
|
*/
|
|
protected function getTraitsForModel(string $className): array
|
|
{
|
|
$traits = ['HasFactory']; // Always include HasFactory
|
|
|
|
// Add SoftDeletes for most models
|
|
$traits[] = 'HasSoftDeletes';
|
|
|
|
// Add caching if requested
|
|
if ($this->option('cached')) {
|
|
$traits[] = 'HasCaching';
|
|
}
|
|
|
|
// Add location trait for location-based models
|
|
if (in_array($className, ['Park', 'Company', 'ParkArea'])) {
|
|
$traits[] = 'HasLocation';
|
|
}
|
|
|
|
// Add slug history for main entities
|
|
if (in_array($className, ['Park', 'Ride', 'Company', 'Designer'])) {
|
|
$traits[] = 'HasSlugHistory';
|
|
}
|
|
|
|
// Add statistics for countable entities
|
|
if (in_array($className, ['Park', 'Ride', 'User'])) {
|
|
$traits[] = 'HasStatistics';
|
|
}
|
|
|
|
return $traits;
|
|
}
|
|
|
|
/**
|
|
* Build trait import statements
|
|
*/
|
|
protected function buildTraitImports(array $traits): string
|
|
{
|
|
$imports = [];
|
|
foreach ($traits as $trait) {
|
|
if (isset($this->thrillWikiTraits[$trait])) {
|
|
$imports[] = "use {$this->thrillWikiTraits[$trait]};";
|
|
}
|
|
}
|
|
return implode("\n", $imports);
|
|
}
|
|
|
|
/**
|
|
* Build trait use statements
|
|
*/
|
|
protected function buildTraitUses(array $traits): string
|
|
{
|
|
$uses = array_map(function($trait) {
|
|
return " use {$trait};";
|
|
}, $traits);
|
|
|
|
return implode("\n", $uses);
|
|
}
|
|
|
|
/**
|
|
* Get relationships for the model
|
|
*/
|
|
protected function getRelationshipsForModel(string $className): string
|
|
{
|
|
if (!$this->option('with-relationships')) {
|
|
return '';
|
|
}
|
|
|
|
if (!isset($this->relationshipPatterns[$className])) {
|
|
return '';
|
|
}
|
|
|
|
$relationships = [];
|
|
foreach ($this->relationshipPatterns[$className] as $method => $definition) {
|
|
$relationships[] = $this->buildRelationshipMethod($method, $definition);
|
|
}
|
|
|
|
return "\n // Relationships\n\n" . implode("\n\n", $relationships);
|
|
}
|
|
|
|
/**
|
|
* Build a relationship method
|
|
*/
|
|
protected function buildRelationshipMethod(string $method, string $definition): string
|
|
{
|
|
[$type, $model, $foreignKey] = array_pad(explode(',', str_replace(':', ',', $definition)), 3, null);
|
|
|
|
$methodBody = match($type) {
|
|
'hasMany' => $foreignKey ?
|
|
"return \$this->hasMany({$model}::class, '{$foreignKey}');" :
|
|
"return \$this->hasMany({$model}::class);",
|
|
'belongsTo' => $foreignKey ?
|
|
"return \$this->belongsTo({$model}::class, '{$foreignKey}');" :
|
|
"return \$this->belongsTo({$model}::class);",
|
|
'hasManyThrough' => "return \$this->hasManyThrough({$model}::class, {$foreignKey}::class);",
|
|
'morphMany' => "return \$this->morphMany({$model}::class, 'morphable');",
|
|
'morphTo' => "return \$this->morphTo();",
|
|
default => "return \$this->{$type}({$model}::class);"
|
|
};
|
|
|
|
return " /**\n * Get the {$method} relationship.\n */\n public function {$method}()\n {\n {$methodBody}\n }";
|
|
}
|
|
|
|
/**
|
|
* Get caching methods for the model
|
|
*/
|
|
protected function getCachingMethods(string $className): string
|
|
{
|
|
return <<<'PHP'
|
|
|
|
// Caching Methods
|
|
|
|
/**
|
|
* Remember a value in cache with model-specific key.
|
|
*/
|
|
public function remember(string $key, $callback, int $ttl = 3600)
|
|
{
|
|
return cache()->remember($this->getCacheKey($key), $ttl, $callback);
|
|
}
|
|
|
|
/**
|
|
* Invalidate cache for this model.
|
|
*/
|
|
public function invalidateCache(string $key = null): void
|
|
{
|
|
if ($key) {
|
|
cache()->forget($this->getCacheKey($key));
|
|
} else {
|
|
// Clear all cache keys for this model
|
|
cache()->forget($this->getCacheKey());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Boot method to handle cache invalidation.
|
|
*/
|
|
protected static function boot()
|
|
{
|
|
parent::boot();
|
|
|
|
static::saved(function ($model) {
|
|
$model->invalidateCache();
|
|
});
|
|
|
|
static::deleted(function ($model) {
|
|
$model->invalidateCache();
|
|
});
|
|
}
|
|
PHP;
|
|
}
|
|
|
|
/**
|
|
* Generate migration file
|
|
*/
|
|
protected function generateMigration(string $className, string $tableName): void
|
|
{
|
|
$migrationName = 'create_' . $tableName . '_table';
|
|
$timestamp = date('Y_m_d_His');
|
|
$migrationFile = database_path("migrations/{$timestamp}_{$migrationName}.php");
|
|
|
|
$migrationContent = $this->buildMigrationContent($className, $tableName, $migrationName);
|
|
|
|
File::put($migrationFile, $migrationContent);
|
|
$this->line("✅ Migration created: database/migrations/{$timestamp}_{$migrationName}.php");
|
|
}
|
|
|
|
/**
|
|
* Build migration content
|
|
*/
|
|
protected function buildMigrationContent(string $className, string $tableName, string $migrationName): string
|
|
{
|
|
$migrationClass = Str::studly($migrationName);
|
|
|
|
$stub = <<<'PHP'
|
|
<?php
|
|
|
|
use Illuminate\Database\Migrations\Migration;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
return new class extends Migration
|
|
{
|
|
/**
|
|
* Run the migrations.
|
|
*/
|
|
public function up(): void
|
|
{
|
|
Schema::create('{TABLE_NAME}', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('name');
|
|
$table->text('description')->nullable();
|
|
$table->boolean('is_active')->default(true);
|
|
|
|
// Add common ThrillWiki fields
|
|
$table->string('slug')->unique();
|
|
|
|
// Add indexes for performance
|
|
$table->index(['is_active']);
|
|
$table->index(['name']);
|
|
$table->index(['slug']);
|
|
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reverse the migrations.
|
|
*/
|
|
public function down(): void
|
|
{
|
|
Schema::dropIfExists('{TABLE_NAME}');
|
|
}
|
|
};
|
|
PHP;
|
|
|
|
return str_replace('{TABLE_NAME}', $tableName, $stub);
|
|
}
|
|
|
|
/**
|
|
* Generate factory file
|
|
*/
|
|
protected function generateFactory(string $className): void
|
|
{
|
|
$factoryPath = database_path("factories/{$className}Factory.php");
|
|
|
|
if (File::exists($factoryPath) && !$this->option('force')) {
|
|
$this->error("Factory {$className}Factory already exists! Use --force to overwrite.");
|
|
return;
|
|
}
|
|
|
|
$factoryContent = $this->buildFactoryContent($className);
|
|
|
|
$this->ensureDirectoryExists(dirname($factoryPath));
|
|
File::put($factoryPath, $factoryContent);
|
|
|
|
$this->line("✅ Factory created: database/factories/{$className}Factory.php");
|
|
}
|
|
|
|
/**
|
|
* Build factory content
|
|
*/
|
|
protected function buildFactoryContent(string $className): string
|
|
{
|
|
$stub = <<<'PHP'
|
|
<?php
|
|
|
|
namespace Database\Factories;
|
|
|
|
use App\Models\{CLASS_NAME};
|
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\{CLASS_NAME}>
|
|
*/
|
|
class {CLASS_NAME}Factory extends Factory
|
|
{
|
|
/**
|
|
* The name of the factory's corresponding model.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $model = {CLASS_NAME}::class;
|
|
|
|
/**
|
|
* Define the model's default state.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function definition(): array
|
|
{
|
|
$name = $this->faker->unique()->words(2, true);
|
|
|
|
return [
|
|
'name' => $name,
|
|
'slug' => Str::slug($name),
|
|
'description' => $this->faker->paragraphs(2, true),
|
|
'is_active' => $this->faker->boolean(90), // 90% chance of being active
|
|
'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
|
|
'updated_at' => function (array $attributes) {
|
|
return $this->faker->dateTimeBetween($attributes['created_at'], 'now');
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Indicate that the model is active.
|
|
*/
|
|
public function active(): static
|
|
{
|
|
return $this->state(fn (array $attributes) => [
|
|
'is_active' => true,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Indicate that the model is inactive.
|
|
*/
|
|
public function inactive(): static
|
|
{
|
|
return $this->state(fn (array $attributes) => [
|
|
'is_active' => false,
|
|
]);
|
|
}
|
|
}
|
|
PHP;
|
|
|
|
return str_replace('{CLASS_NAME}', $className, $stub);
|
|
}
|
|
|
|
/**
|
|
* Generate API resource
|
|
*/
|
|
protected function generateApiResource(string $className): void
|
|
{
|
|
$resourcePath = app_path("Http/Resources/{$className}Resource.php");
|
|
|
|
if (File::exists($resourcePath) && !$this->option('force')) {
|
|
$this->error("Resource {$className}Resource already exists! Use --force to overwrite.");
|
|
return;
|
|
}
|
|
|
|
$resourceContent = $this->buildApiResourceContent($className);
|
|
|
|
$this->ensureDirectoryExists(dirname($resourcePath));
|
|
File::put($resourcePath, $resourceContent);
|
|
|
|
$this->line("✅ API Resource created: app/Http/Resources/{$className}Resource.php");
|
|
}
|
|
|
|
/**
|
|
* Build API resource content
|
|
*/
|
|
protected function buildApiResourceContent(string $className): string
|
|
{
|
|
$stub = <<<'PHP'
|
|
<?php
|
|
|
|
namespace App\Http\Resources;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Resources\Json\JsonResource;
|
|
|
|
/**
|
|
* {CLASS_NAME} API Resource
|
|
*
|
|
* Transforms {CLASS_NAME} model data for API responses
|
|
* Includes ThrillWiki optimization patterns
|
|
*/
|
|
class {CLASS_NAME}Resource extends JsonResource
|
|
{
|
|
/**
|
|
* Transform the resource into an array.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function toArray(Request $request): array
|
|
{
|
|
return [
|
|
'id' => $this->id,
|
|
'name' => $this->name,
|
|
'slug' => $this->slug,
|
|
'description' => $this->description,
|
|
'is_active' => $this->is_active,
|
|
'created_at' => $this->created_at?->toISOString(),
|
|
'updated_at' => $this->updated_at?->toISOString(),
|
|
|
|
// Include relationships when loaded
|
|
$this->mergeWhen($this->relationLoaded('relationships'), [
|
|
// Add relationship data here
|
|
]),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get additional data that should be returned with the resource array.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function with(Request $request): array
|
|
{
|
|
return [
|
|
'meta' => [
|
|
'model' => '{CLASS_NAME}',
|
|
'generated_at' => now()->toISOString(),
|
|
],
|
|
];
|
|
}
|
|
}
|
|
PHP;
|
|
|
|
return str_replace('{CLASS_NAME}', $className, $stub);
|
|
}
|
|
|
|
/**
|
|
* Generate test files
|
|
*/
|
|
protected function generateTests(string $className): void
|
|
{
|
|
$testPath = base_path("tests/Feature/{$className}Test.php");
|
|
|
|
if (File::exists($testPath) && !$this->option('force')) {
|
|
$this->error("Test {$className}Test already exists! Use --force to overwrite.");
|
|
return;
|
|
}
|
|
|
|
$testContent = $this->buildTestContent($className);
|
|
|
|
$this->ensureDirectoryExists(dirname($testPath));
|
|
File::put($testPath, $testContent);
|
|
|
|
$this->line("✅ Test created: tests/Feature/{$className}Test.php");
|
|
}
|
|
|
|
/**
|
|
* Build test content
|
|
*/
|
|
protected function buildTestContent(string $className): string
|
|
{
|
|
$stub = <<<'PHP'
|
|
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\{CLASS_NAME};
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Foundation\Testing\WithFaker;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* {CLASS_NAME} Model Feature Tests
|
|
*
|
|
* Tests for ThrillWiki {CLASS_NAME} model functionality
|
|
*/
|
|
class {CLASS_NAME}Test extends TestCase
|
|
{
|
|
use RefreshDatabase, WithFaker;
|
|
|
|
/**
|
|
* Test model creation.
|
|
*/
|
|
public function test_can_create_{LOWER_CLASS_NAME}(): void
|
|
{
|
|
${LOWER_CLASS_NAME} = {CLASS_NAME}::factory()->create();
|
|
|
|
$this->assertDatabaseHas('{TABLE_NAME}', [
|
|
'id' => ${LOWER_CLASS_NAME}->id,
|
|
'name' => ${LOWER_CLASS_NAME}->name,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Test model factory.
|
|
*/
|
|
public function test_{LOWER_CLASS_NAME}_factory_works(): void
|
|
{
|
|
${LOWER_CLASS_NAME} = {CLASS_NAME}::factory()->create();
|
|
|
|
$this->assertInstanceOf({CLASS_NAME}::class, ${LOWER_CLASS_NAME});
|
|
$this->assertNotEmpty(${LOWER_CLASS_NAME}->name);
|
|
$this->assertIsBool(${LOWER_CLASS_NAME}->is_active);
|
|
}
|
|
|
|
/**
|
|
* Test active scope.
|
|
*/
|
|
public function test_active_scope_filters_correctly(): void
|
|
{
|
|
{CLASS_NAME}::factory()->active()->create();
|
|
{CLASS_NAME}::factory()->inactive()->create();
|
|
|
|
$activeCount = {CLASS_NAME}::active()->count();
|
|
$totalCount = {CLASS_NAME}::count();
|
|
|
|
$this->assertEquals(1, $activeCount);
|
|
$this->assertEquals(2, $totalCount);
|
|
}
|
|
|
|
/**
|
|
* Test cache key generation.
|
|
*/
|
|
public function test_cache_key_generation(): void
|
|
{
|
|
${LOWER_CLASS_NAME} = {CLASS_NAME}::factory()->create();
|
|
|
|
$cacheKey = ${LOWER_CLASS_NAME}->getCacheKey();
|
|
$expectedKey = strtolower('{LOWER_CLASS_NAME}') . '.' . ${LOWER_CLASS_NAME}->id;
|
|
|
|
$this->assertEquals($expectedKey, $cacheKey);
|
|
}
|
|
|
|
/**
|
|
* Test cache key with suffix.
|
|
*/
|
|
public function test_cache_key_with_suffix(): void
|
|
{
|
|
${LOWER_CLASS_NAME} = {CLASS_NAME}::factory()->create();
|
|
|
|
$cacheKey = ${LOWER_CLASS_NAME}->getCacheKey('details');
|
|
$expectedKey = strtolower('{LOWER_CLASS_NAME}') . '.' . ${LOWER_CLASS_NAME}->id . '.details';
|
|
|
|
$this->assertEquals($expectedKey, $cacheKey);
|
|
}
|
|
|
|
/**
|
|
* Test soft deletes.
|
|
*/
|
|
public function test_soft_deletes_work(): void
|
|
{
|
|
${LOWER_CLASS_NAME} = {CLASS_NAME}::factory()->create();
|
|
${LOWER_CLASS_NAME}->delete();
|
|
|
|
$this->assertSoftDeleted(${LOWER_CLASS_NAME});
|
|
|
|
// Test that it's excluded from normal queries
|
|
$this->assertEquals(0, {CLASS_NAME}::count());
|
|
|
|
// Test that it's included in withTrashed queries
|
|
$this->assertEquals(1, {CLASS_NAME}::withTrashed()->count());
|
|
}
|
|
}
|
|
PHP;
|
|
|
|
return str_replace([
|
|
'{CLASS_NAME}',
|
|
'{LOWER_CLASS_NAME}',
|
|
'{TABLE_NAME}',
|
|
], [
|
|
$className,
|
|
strtolower($className),
|
|
Str::snake(Str::plural($className)),
|
|
], $stub);
|
|
}
|
|
|
|
/**
|
|
* Ensure directory exists
|
|
*/
|
|
protected function ensureDirectoryExists(string $directory): void
|
|
{
|
|
if (!File::isDirectory($directory)) {
|
|
File::makeDirectory($directory, 0755, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display summary of generated files
|
|
*/
|
|
protected function displaySummary(string $className): void
|
|
{
|
|
$this->newLine();
|
|
$this->info("🎉 ThrillWiki Model '{$className}' created successfully!");
|
|
$this->newLine();
|
|
|
|
$this->line("📁 Files Generated:");
|
|
$this->line(" • app/Models/{$className}.php");
|
|
|
|
if ($this->option('migration')) {
|
|
$this->line(" • database/migrations/[timestamp]_create_" . Str::snake(Str::plural($className)) . "_table.php");
|
|
}
|
|
|
|
if ($this->option('factory')) {
|
|
$this->line(" • database/factories/{$className}Factory.php");
|
|
}
|
|
|
|
if ($this->option('api-resource')) {
|
|
$this->line(" • app/Http/Resources/{$className}Resource.php");
|
|
}
|
|
|
|
if ($this->option('with-tests')) {
|
|
$this->line(" • tests/Feature/{$className}Test.php");
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->line("🚀 Next Steps:");
|
|
|
|
if ($this->option('migration')) {
|
|
$this->line(" 1. Run migration: php artisan migrate");
|
|
}
|
|
|
|
if ($this->option('with-tests')) {
|
|
$this->line(" 2. Run tests: php artisan test --filter {$className}Test");
|
|
}
|
|
|
|
$this->line(" 3. Customize model attributes and relationships");
|
|
$this->line(" 4. Update migration with specific fields");
|
|
|
|
if ($this->option('factory')) {
|
|
$this->line(" 5. Customize factory with realistic data");
|
|
}
|
|
|
|
$this->newLine();
|
|
}
|
|
} |