如何在Laravel中获取所有模型的列表?

16

我希望能够找到 Laravel 项目中所有模型和数据库表的列表备份。

我想这样做是为了构建一个仪表盘,显示所有模型数据随时间变化的方式,例如,如果有一个用户模型,过去90天每天新增或修改了多少用户。

9个回答

27

我建议使用PHP反射来代替依赖于所有模型都位于名为Model的命名空间或目录中的方法。

下面的代码示例收集所有应用程序类,验证它们是否实际扩展了Eloquent模型类并且不是抽象的。

<?php

use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;

function getModels(): Collection
{
    $models = collect(File::allFiles(app_path()))
        ->map(function ($item) {
            $path = $item->getRelativePathName();
            $class = sprintf('\%s%s',
                Container::getInstance()->getNamespace(),
                strtr(substr($path, 0, strrpos($path, '.')), '/', '\\'));

            return $class;
        })
        ->filter(function ($class) {
            $valid = false;

            if (class_exists($class)) {
                $reflection = new \ReflectionClass($class);
                $valid = $reflection->isSubclassOf(Model::class) &&
                    !$reflection->isAbstract();
            }

            return $valid;
        });

    return $models->values();
}

4
这绝对是此处最强大的选择。谢谢。 - Joel Mellon
1
谢谢!到目前为止,这是最强大的答案,尽管它将缺少包提供的模型(例如spatie权限/标签等)。 - ajthinking
@RibeiroBreno 我的一个目录中有一个特征,它在行$valid = $reflection->isSubclassOf(Model::class)处报错“_Class App\Models\Traits\Model does not exist_”。 - Daniyal Nasir
你好 @DaniyalNasir! 这看起来像是一个命名空间问题。 你是否在上面包含了所有的"use"语句? - RibeiroBreno
不错,@DmitryGultyaev!这个Symfony库已经被弃用并移至Composer。请参见:
  • https://github.com/symfony/class-loader/blob/3.4/ClassMapGenerator.php#L14
  • https://github.com/composer/composer/blob/main/src/Composer/Autoload/ClassMapGenerator.php
- RibeiroBreno
显示剩余2条评论

21

我会浏览你的文件系统并从 models 文件夹中提取所有的php文件。我将我的模型保存在 app/Models 文件夹中,所以可能是这样的:

$path = app_path() . "/Models";

function getModels($path){
    $out = [];
    $results = scandir($path);
    foreach ($results as $result) {
        if ($result === '.' or $result === '..') continue;
        $filename = $path . '/' . $result;
        if (is_dir($filename)) {
            $out = array_merge($out, getModels($filename));
        }else{
            $out[] = substr($filename,0,-4);
        }
    }
    return $out;
}

dd(getModels($path));

我刚试了一下,它输出了我所有模型的完整文件路径。如果您只想查看命名空间和模型名称,您可以剥离字符串以使其只显示这些内容。


好的!这确实对我有所帮助,我可能会用到它。 - Tommy Nicholas
13
第一行可以使用app_path('Models')代替字符串连接, Laravel 的方式是使用File::allFiles($path)代替自己编写那些逻辑。 - Lupinity Labs

7
我正在查找可读性较好的内容,所以我编写了这段代码,用于获取所有模型名称,你可以按照自己的需求进行格式化。
<?php

function getModelNames(): array
{
    $path = app_path('Models') . '/*.php';
    return collect(glob($path))->map(fn ($file) => basename($file, '.php'))->toArray();
}

很好,很简短。谢谢。 - Ali Azimi

7
请注意,这可能会错过在引导期间未在范围内的模型。请参见下面的编辑。
有一种方法可以在不迭代文件系统的情况下加载已声明的模型。由于大多数模型都是在引导之后声明的,因此您可以调用get_declared_classes并过滤返回值以获取您模型的命名空间(我的类在\App\Models命名空间中):
$models   = collect(get_declared_classes())->filter(function ($item) {
    return (substr($item, 0, 11) === 'App\Models\\');
});

如@ChronoFish所述,这种方法并不总是能够检索到所有模型。
例如,在服务提供商的早期阶段,它根本不起作用。即使在它正常工作时,它可能会错过一些模型,这取决于您项目的结构,因为并非所有类都始终处于范围内。对于我的测试项目,所有模型都已加载,但在更复杂的应用程序中,可能会错过相当数量的类。
感谢您的评论!
我目前正在使用类似于以下内容的东西:
        $appNamespace = Illuminate\Container\Container::getInstance()->getNamespace();
        $modelNamespace = 'Models';

        $models = collect(File::allFiles(app_path($modelNamespace)))->map(function ($item) use ($appNamespace, $modelNamespace) {
            $rel   = $item->getRelativePathName();
            $class = sprintf('\%s%s%s', $appNamespace, $modelNamespace ? $modelNamespace . '\\' : '',
                implode('\\', explode('/', substr($rel, 0, strrpos($rel, '.')))));
            return class_exists($class) ? $class : null;
        })->filter();

$modelNamespace是为那些在其模型中有不同文件夹和命名空间的人准备的,这是强烈推荐的做法,或者使用 Laravel 8+ 的人可以使用此功能。那些只使用 Laravel 7 或更低版本的默认设置可以将其留空,但这样会导入应用程序目录中的所有类,而不仅仅是 Eloquent 模型。

在这种情况下,您可以通过像这样过滤列表来确保仅获取模型:

$models->filter(
    function ($model) {
        return !is_subclass_of($model, Illuminate\Database\Eloquent\Model::class);
    }
);

2
我认为这只会给你实例化的类。它不会给你一个项目中的所有模型,而这恰恰是 OP 所要求的。 - ChronoFish
@ChronoFish 类不必被实例化,只需声明(在作用域中定义)。一旦 Laravel 加载了所有模型依赖项,你的所有模型都可以通过 get_declared_classes 访问。但需要注意的是,在 Laravel 引导生命周期的某些早期阶段,这还不是事实(例如在运行服务提供程序时)。我会在我的回答中包含这个信息以进行澄清。 - Lupinity Labs
1
我注意到 Laravel 似乎并不积极地引导模型类。在我的测试项目中,所有可用的模型都被声明了,但在另一个项目中,有些模型却缺失了,因此根据你的项目情况,这可能不是适合你的正确选择,感谢你的评论! - Lupinity Labs
聪明的方法,喜欢这个想法,不知道 is_subclass_of 方法,这解决了我的问题。 - Szabi Zsoldos

5

根据题目答案如下:

<?php

use Illuminate\Support\Facades\File;

/**
 * Get Available models in app.
 * 
 * @return array $models
 */
public static function getAvailableModels()
{

    $models = [];
    $modelsPath = app_path('Models');
    $modelFiles = File::allFiles($modelsPath);
    foreach ($modelFiles as $modelFile) {
        $models[] = '\App\\' . $modelFile->getFilenameWithoutExtension();
    }

    return $models;
}

2
如果应用程序有添加更多模型的服务提供商,则此答案会忽略它们。(还包括应用程序开发者决定使用不同路径进行建模的情况)。 - Christian

4
一种方法是使用 标准 PHP 库 (SPL) 递归列出目录中的所有文件,你可以编写以下类似的函数。
function getModels($path, $namespace){
        $out = [];

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator(
                $path
            ), \RecursiveIteratorIterator::SELF_FIRST
        );
        foreach ($iterator as $item) {
            /**
             * @var \SplFileInfo $item
             */
            if($item->isReadable() && $item->isFile() && mb_strtolower($item->getExtension()) === 'php'){
                $out[] =  $namespace .
                    str_replace("/", "\\", mb_substr($item->getRealPath(), mb_strlen($path), -4));
            }
        }
        return $out;
}
getModels(app_path("Models/"), "App\\Models\\");

1
你应该添加一些关于你正在做什么以及OP犯了什么错误的描述。 - Muhammad Omer Aslam
使用SPL递归列出目录中的所有文件。 https://secure.php.net/manual/en/book.spl.php - Agel_Nash

1

0

这是我的解决方案。不打算在子文件夹中搜索模型。

<?php

use Illuminate\Database\Eloquent\Model;

function getModels(?string $path = null): array
    {
        $path = $path ?? app_path();

        // Se obtienen los archivos en el directorio, sin subdirectorios.
        $files = File::files($path);

        // Se obtienen los nombres sin rutas ni extensiones.
        $filenames = array_map(function ($file): string {
            $pathinfo = pathinfo($file);

            return $pathinfo['filename'];
        }, $files);

        $namespace = "\App";

        // Se conservan aquellos que sean modelos.
        $mapped = array_map(
            function ($filename) use ($namespace): ?string {
                $class = "{$namespace}\\{$filename}";

                if (!class_exists($class)) {
                    return null;
                }

                $object = new $class;

                if (!$object instanceof Model) {
                    return null;
                }

                return $class;
            },
            $filenames
        );

        // Se filtran los que no son modelos.
        $models = array_filter($mapped);

        return array_values($models);
    }

0

我认为这样更好、更简单(不使用反射类)

    function get_all_models(): array
    {
        $namespace = app()->getNamespace() . "Models\\";
        $files     = File::allFiles(app_path('Models'));
        return collect($files)
             ->map(function (SplFileInfo $file) use ($namespace) {
                 $filePath = str_replace('.' . $file->getExtension(), '', $file->getRelativePathname());
                 return $namespace . str_replace('/', '\\', $filePath);
             })
             ->filter(function ($class) {
                 return class_exists($class) && is_subclass_of($class, Model::class);
             })->values()->toArray();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接