view src/Generator/Factory/FactoryGenerator.php @ 40:2cf26b593f4a ls_dev_2025_09 tip

better support for different column types
author Luka Sitas <sitas.luka.97@gmail.com>
date Thu, 16 Oct 2025 10:54:04 -0400
parents b5c6ebd33547
children
line wrap: on
line source

<?php

namespace Wizard\MagicForger\Generator\Factory;

use Illuminate\Support\Str;
use Symfony\Component\Console\Attribute\AsCommand;
use Wizard\MagicForger\Generator\BaseGenerator;
use Wizard\MagicForger\Helpers\RelationshipNavigator;

#[AsCommand(name: 'mf:factory')]
class FactoryGenerator extends BaseGenerator
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $name = 'mf:factory';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Generates the Factory File for a table.';

    /**
     * The type of class being generated.
     *
     * @var string
     */
    protected $type = 'Factory';

    protected static $cached_snippets = [];

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // Delegate to parent handler (includes replacements and insertions)
        return parent::handle();
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return $this->resolveStubPath('/stubs/factory.stub');
    }

    /**
     * Resolve the fully-qualified path to the stub.
     *
     * @param  string  $stub
     * @return string
     */
    protected function resolveStubPath($stub)
    {
        return is_file($customPath = $this->laravel->basePath(trim($stub, '/')))
            ? $customPath
            : __DIR__.$stub;
    }

    protected function getClassName($name)
    {
        return $this->factory_name($name);
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getPath($name = null)
    {
        return str_replace(['App\\', '\\'], ['app/', '/'], $this->getFactoryNamespace().'/'.$this->factory_name($this->getTableInput()).'.php');
    }

    protected function gatherRelations()
    {
        $relations = RelationshipNavigator::getRelations($this->getCurrentTable());
        foreach ($relations as $relation_type => $relation_values) {
            $relations[$relation_type] = array_column($relation_values, null, 'column');
        }

        return $relations;
    }

    protected function renderColumns()
    {
        $values = [];
        $relations = $this->gatherRelations();

        foreach ($this->get_columns() as $column) {
            if (in_array($column['name'], $this->columns_to_ignore)) {
                continue;
            }

            $value = $this->getFakeValue($column, $relations);
            $snippet = $this->getSnippet('value');

            // Replace placeholders with actual values
            $values[] = str_replace(['{{value}}', '{{column_name}}'], [$value, $column['name']], $snippet);
        }

        return implode("\n", $values);
    }

    protected function getFakeValue($column, $relations)
    {
        $value = '$this->faker';
        $nullable = ($column['nullable'] ? '->optional($weight = 0.5)' : '');

        switch ($column['type_name']) {
            case 'date':
                return "$value$nullable->date()";
            case 'timestamp':
                return "$value$nullable->timestamp()";
            case 'tinyint':
                return "$value$nullable->numberBetween(0,1)";
            case 'bigint':
                if (isset($relations['belongsTo'][$column['name']])) {
                    $related = $this->getNamespacedModel($relations['belongsTo'][$column['name']]['table']);
                    return "$related::factory()";
                }
                // fall through to int for lack of better option
            case 'int':
                return "$value$nullable->randomNumber()";
            case 'float':
                return "$value$nullable->randomFloat()";
            default:
                return "$value$nullable->text()";
        }
    }

    /**
     * Get available insertions including model relationships.
     */
    public function get_available_inserts(): array
    {
        // Merge parent insertions (attributes, fillable, etc.)
        $inserts = parent::get_available_inserts();

        // Gather and render relationships for this model
        $columns = $this->renderColumns();

        // Assign to stub placeholders
        $inserts['# {{ factoryInsertPoint }}'] = $columns;

        return $inserts;
    }
}