Revision 148 | Blame | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed
<?phpnamespace Illuminate\Foundation\Console;use BackedEnum;use Doctrine\DBAL\Schema\Column;use Doctrine\DBAL\Schema\Index;use Doctrine\DBAL\Types\DecimalType;use Illuminate\Console\Command;use Illuminate\Contracts\Container\BindingResolutionException;use Illuminate\Database\Console\DatabaseInspectionCommand;use Illuminate\Database\Eloquent\Relations\Relation;use Illuminate\Support\Facades\Gate;use Illuminate\Support\Str;use ReflectionClass;use ReflectionMethod;use SplFileObject;use Symfony\Component\Console\Attribute\AsCommand;use Symfony\Component\Console\Output\OutputInterface;use UnitEnum;#[AsCommand(name: 'model:show')]class ShowModelCommand extends DatabaseInspectionCommand{/*** The console command name.** @var string*/protected $name = 'model:show {model}';/*** The name of the console command.** This name is used to identify the command during lazy loading.** @var string|null** @deprecated*/protected static $defaultName = 'model:show';/*** The console command description.** @var string*/protected $description = 'Show information about an Eloquent model';/*** The console command signature.** @var string*/protected $signature = 'model:show {model : The model to show}{--database= : The database connection to use}{--json : Output the model as JSON}';/*** The methods that can be called in a model to indicate a relation.** @var array*/protected $relationMethods = ['hasMany','hasManyThrough','hasOneThrough','belongsToMany','hasOne','belongsTo','morphOne','morphTo','morphMany','morphToMany','morphedByMany',];/*** Execute the console command.** @return void*/public function handle(){if (! $this->ensureDependenciesExist()) {return 1;}$class = $this->qualifyModel($this->argument('model'));try {$model = $this->laravel->make($class);$class = get_class($model);} catch (BindingResolutionException $e) {return $this->components->error($e->getMessage());}if ($this->option('database')) {$model->setConnection($this->option('database'));}$this->display($class,$model->getConnection()->getName(),$model->getConnection()->getTablePrefix().$model->getTable(),$this->getPolicy($model),$this->getAttributes($model),$this->getRelations($model),$this->getObservers($model),);}/*** Get the first policy associated with this model.** @param \Illuminate\Database\Eloquent\Model $model* @return Illuminate\Support\Collection*/protected function getPolicy($model){return collect(Gate::policies())->filter(fn ($policy, $modelClass) => $modelClass === get_class($model))->values()->first();}/*** Get the column attributes for the given model.** @param \Illuminate\Database\Eloquent\Model $model* @return \Illuminate\Support\Collection*/protected function getAttributes($model){$schema = $model->getConnection()->getDoctrineSchemaManager();$this->registerTypeMappings($schema->getDatabasePlatform());$table = $model->getConnection()->getTablePrefix().$model->getTable();$columns = $schema->listTableColumns($table);$indexes = $schema->listTableIndexes($table);return collect($columns)->values()->map(fn (Column $column) => ['name' => $column->getName(),'type' => $this->getColumnType($column),'increments' => $column->getAutoincrement(),'nullable' => ! $column->getNotnull(),'default' => $this->getColumnDefault($column, $model),'unique' => $this->columnIsUnique($column->getName(), $indexes),'fillable' => $model->isFillable($column->getName()),'hidden' => $this->attributeIsHidden($column->getName(), $model),'appended' => null,'cast' => $this->getCastType($column->getName(), $model),])->merge($this->getVirtualAttributes($model, $columns));}/*** Get the virtual (non-column) attributes for the given model.** @param \Illuminate\Database\Eloquent\Model $model* @param \Doctrine\DBAL\Schema\Column[] $columns* @return \Illuminate\Support\Collection*/protected function getVirtualAttributes($model, $columns){$class = new ReflectionClass($model);return collect($class->getMethods())->reject(fn (ReflectionMethod $method) => $method->isStatic()|| $method->isAbstract()|| $method->getDeclaringClass()->getName() !== get_class($model))->mapWithKeys(function (ReflectionMethod $method) use ($model) {if (preg_match('/^get(.+)Attribute$/', $method->getName(), $matches) === 1) {return [Str::snake($matches[1]) => 'accessor'];} elseif ($model->hasAttributeMutator($method->getName())) {return [Str::snake($method->getName()) => 'attribute'];} else {return [];}})->reject(fn ($cast, $name) => collect($columns)->has($name))->map(fn ($cast, $name) => ['name' => $name,'type' => null,'increments' => false,'nullable' => null,'default' => null,'unique' => null,'fillable' => $model->isFillable($name),'hidden' => $this->attributeIsHidden($name, $model),'appended' => $model->hasAppended($name),'cast' => $cast,])->values();}/*** Get the relations from the given model.** @param \Illuminate\Database\Eloquent\Model $model* @return \Illuminate\Support\Collection*/protected function getRelations($model){return collect(get_class_methods($model))->map(fn ($method) => new ReflectionMethod($model, $method))->reject(fn (ReflectionMethod $method) => $method->isStatic()|| $method->isAbstract()|| $method->getDeclaringClass()->getName() !== get_class($model))->filter(function (ReflectionMethod $method) {$file = new SplFileObject($method->getFileName());$file->seek($method->getStartLine() - 1);$code = '';while ($file->key() < $method->getEndLine()) {$code .= trim($file->current());$file->next();}return collect($this->relationMethods)->contains(fn ($relationMethod) => str_contains($code, '$this->'.$relationMethod.'('));})->map(function (ReflectionMethod $method) use ($model) {$relation = $method->invoke($model);if (! $relation instanceof Relation) {return null;}return ['name' => $method->getName(),'type' => Str::afterLast(get_class($relation), '\\'),'related' => get_class($relation->getRelated()),];})->filter()->values();}/*** Get the Observers watching this model.** @param \Illuminate\Database\Eloquent\Model $model* @return Illuminate\Support\Collection*/protected function getObservers($model){$listeners = $this->getLaravel()->make('events')->getRawListeners();// Get the Eloquent observers for this model...$listeners = array_filter($listeners, function ($v, $key) use ($model) {return Str::startsWith($key, 'eloquent.') && Str::endsWith($key, $model::class);}, ARRAY_FILTER_USE_BOTH);// Format listeners Eloquent verb => Observer methods...$extractVerb = function ($key) {preg_match('/eloquent.([a-zA-Z]+)\: /', $key, $matches);return $matches[1] ?? '?';};$formatted = [];foreach ($listeners as $key => $observerMethods) {$formatted[] = ['event' => $extractVerb($key),'observer' => array_map(fn ($obs) => is_string($obs) ? $obs : 'Closure', $observerMethods),];}return collect($formatted);}/*** Render the model information.** @param string $class* @param string $database* @param string $table* @param string $policy* @param \Illuminate\Support\Collection $attributes* @param \Illuminate\Support\Collection $relations* @param \Illuminate\Support\Collection $observers* @return void*/protected function display($class, $database, $table, $policy, $attributes, $relations, $observers){$this->option('json')? $this->displayJson($class, $database, $table, $policy, $attributes, $relations, $observers): $this->displayCli($class, $database, $table, $policy, $attributes, $relations, $observers);}/*** Render the model information as JSON.** @param string $class* @param string $database* @param string $table* @param string $policy* @param \Illuminate\Support\Collection $attributes* @param \Illuminate\Support\Collection $relations* @param \Illuminate\Support\Collection $observers* @return void*/protected function displayJson($class, $database, $table, $policy, $attributes, $relations, $observers){$this->output->writeln(collect(['class' => $class,'database' => $database,'table' => $table,'policy' => $policy,'attributes' => $attributes,'relations' => $relations,'observers' => $observers,])->toJson());}/*** Render the model information for the CLI.** @param string $class* @param string $database* @param string $table* @param string $policy* @param \Illuminate\Support\Collection $attributes* @param \Illuminate\Support\Collection $relations* @param \Illuminate\Support\Collection $observers* @return void*/protected function displayCli($class, $database, $table, $policy, $attributes, $relations, $observers){$this->newLine();$this->components->twoColumnDetail('<fg=green;options=bold>'.$class.'</>');$this->components->twoColumnDetail('Database', $database);$this->components->twoColumnDetail('Table', $table);if ($policy) {$this->components->twoColumnDetail('Policy', $policy);}$this->newLine();$this->components->twoColumnDetail('<fg=green;options=bold>Attributes</>','type <fg=gray>/</> <fg=yellow;options=bold>cast</>',);foreach ($attributes as $attribute) {$first = trim(sprintf('%s %s',$attribute['name'],collect(['increments', 'unique', 'nullable', 'fillable', 'hidden', 'appended'])->filter(fn ($property) => $attribute[$property])->map(fn ($property) => sprintf('<fg=gray>%s</>', $property))->implode('<fg=gray>,</> ')));$second = collect([$attribute['type'],$attribute['cast'] ? '<fg=yellow;options=bold>'.$attribute['cast'].'</>' : null,])->filter()->implode(' <fg=gray>/</> ');$this->components->twoColumnDetail($first, $second);if ($attribute['default'] !== null) {$this->components->bulletList([sprintf('default: %s', $attribute['default'])],OutputInterface::VERBOSITY_VERBOSE);}}$this->newLine();$this->components->twoColumnDetail('<fg=green;options=bold>Relations</>');foreach ($relations as $relation) {$this->components->twoColumnDetail(sprintf('%s <fg=gray>%s</>', $relation['name'], $relation['type']),$relation['related']);}$this->newLine();$this->components->twoColumnDetail('<fg=green;options=bold>Observers</>');if ($observers->count()) {foreach ($observers as $observer) {$this->components->twoColumnDetail(sprintf('%s', $observer['event']),implode(', ', $observer['observer']));}}$this->newLine();}/*** Get the cast type for the given column.** @param string $column* @param \Illuminate\Database\Eloquent\Model $model* @return string|null*/protected function getCastType($column, $model){if ($model->hasGetMutator($column) || $model->hasSetMutator($column)) {return 'accessor';}if ($model->hasAttributeMutator($column)) {return 'attribute';}return $this->getCastsWithDates($model)->get($column) ?? null;}/*** Get the model casts, including any date casts.** @param \Illuminate\Database\Eloquent\Model $model* @return \Illuminate\Support\Collection*/protected function getCastsWithDates($model){return collect($model->getDates())->filter()->flip()->map(fn () => 'datetime')->merge($model->getCasts());}/*** Get the type of the given column.** @param \Doctrine\DBAL\Schema\Column $column* @return string*/protected function getColumnType($column){$name = $column->getType()->getName();$unsigned = $column->getUnsigned() ? ' unsigned' : '';$details = match (get_class($column->getType())) {DecimalType::class => $column->getPrecision().','.$column->getScale(),default => $column->getLength(),};if ($details) {return sprintf('%s(%s)%s', $name, $details, $unsigned);}return sprintf('%s%s', $name, $unsigned);}/*** Get the default value for the given column.** @param \Doctrine\DBAL\Schema\Column $column* @param \Illuminate\Database\Eloquent\Model $model* @return mixed|null*/protected function getColumnDefault($column, $model){$attributeDefault = $model->getAttributes()[$column->getName()] ?? null;return match (true) {$attributeDefault instanceof BackedEnum => $attributeDefault->value,$attributeDefault instanceof UnitEnum => $attributeDefault->name,default => $attributeDefault ?? $column->getDefault(),};}/*** Determine if the given attribute is hidden.** @param string $attribute* @param \Illuminate\Database\Eloquent\Model $model* @return bool*/protected function attributeIsHidden($attribute, $model){if (count($model->getHidden()) > 0) {return in_array($attribute, $model->getHidden());}if (count($model->getVisible()) > 0) {return ! in_array($attribute, $model->getVisible());}return false;}/*** Determine if the given attribute is unique.** @param string $column* @param \Doctrine\DBAL\Schema\Index[] $indexes* @return bool*/protected function columnIsUnique($column, $indexes){return collect($indexes)->filter(fn (Index $index) => count($index->getColumns()) === 1 && $index->getColumns()[0] === $column)->contains(fn (Index $index) => $index->isUnique());}/*** Qualify the given model class base name.** @param string $model* @return string** @see \Illuminate\Console\GeneratorCommand*/protected function qualifyModel(string $model){if (str_contains($model, '\\') && class_exists($model)) {return $model;}$model = ltrim($model, '\\/');$model = str_replace('/', '\\', $model);$rootNamespace = $this->laravel->getNamespace();if (Str::startsWith($model, $rootNamespace)) {return $model;}return is_dir(app_path('Models'))? $rootNamespace.'Models\\'.$model: $rootNamespace.$model;}}