How do I get a list of all models in Laravel?

26,168

Solution 1

I would navigate through your filesystem and pull out all of the php files from your models folder. I keep my models in the app/Models folder so something like this:

$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));

I just tried this and it spit out the full filepath of all of my models. You could strip the strings to make it only show the namespace and model name if thats what you are looking for.

Solution 2

I would like to suggest a different approach by using PHP reflection instead of relying that all models will reside in a namespace or directory called Model.

The code sample below collects all the application classes, verifies if they actually extend the Eloquent Model class and are not abstract.

<?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();
}

Solution 3

Please be aware that this might miss models that have not been in scope during bootstrapping. Please see the edit below.

There is a way to load the declared models without iterating the file system. Since most of the models are declared after bootstrapping, you may call get_declared_classes and filter the return for your models' namespaces like this (my classes are in \App\Models namespace):

$models   = collect(get_declared_classes())->filter(function ($item) {
    return (substr($item, 0, 11) === 'App\Models\\');
});

As @ChronoFish mentioned, this method does not pull up all models in all cases.

For example, this does not work at all in early stages of the bootstrap lifecycle like in service providers. But even when it is working, it might miss a few models, depending on your project's structure, as not all classes are always in scope. For my test project, all models were loaded, but in a more complex application, a significant number of classes may be missed by this.

Thanks for your comment!

I am currently using something like this:

        $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();

The $modelNamespace is for those who have a distinct folder and namespace for their models, which is highly recommended, or who are using Laravel 8+ where this is the new default. Those who just go with the defaults of Laravel 7 or lower can leave this empty, but will then pull in all classes in the app directory, not just Eloquent models.

In that case you can make sure you only get Models by filtering the list like this:

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

Solution 4

One way could be to use the Standard PHP Library (SPL) to recursively list all files inside a directory you can make a function like below.

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\\");

Solution 5

According of topic answer's:

<?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;
}
Share:
26,168
Tommy Nicholas
Author by

Tommy Nicholas

Updated on August 12, 2021

Comments

  • Tommy Nicholas
    Tommy Nicholas over 2 years

    I would like to find a list of all models, database tables as a backup, in a Laravel project.

    I want to do this to build a dashboard that displays the way data in all of the models has changed over time, I.E. if there is a user model, how many users have been added or modified each day for the last 90 days.

  • Tommy Nicholas
    Tommy Nicholas over 8 years
    Ok! This got my somewhere useful actually I may use this.
  • Muhammad Omer Aslam
    Muhammad Omer Aslam about 6 years
    you should add some description about what are you doing and what the mistake OP was doing
  • Agel_Nash
    Agel_Nash about 6 years
    Recursively list all files within a directory using SPL secure.php.net/manual/en/book.spl.php
  • Lupinity Labs
    Lupinity Labs almost 6 years
    You may use app_path('Models') instead of concatenating a string in the first line, also the Laravel way would be to use File::allFiles($path) instead of writing all that logic yourself.
  • ChronoFish
    ChronoFish over 5 years
    I think this only gives you instantiated classes. It would not give you all models of a project which I think is what the OP asked for.
  • Lupinity Labs
    Lupinity Labs over 5 years
    @ChronoFish The classes don't have to be instantiated, just declared (defined in scope). As soon as Laravel has loaded all model dependencies, all your models will be available through get_declared_classes. It should be mentioned, however, that at certain early points in the Laravel bootstrapping lifecycle this is not yet the case (when running the service providers, for example). I will include this in my answer to clarify.
  • Lupinity Labs
    Lupinity Labs over 5 years
    I noticed that Laravel does not seem to actively bootstrap model classes. In my test project all available models were declared, but in another projects, some were missing, so depending on your project, this might not be the right choice for you, thanks for your comment!
  • Joel Mellon
    Joel Mellon almost 4 years
    This is definitely the most robust option here. Thanks 🤘🏻
  • ajthinking
    ajthinking almost 4 years
    Thanks! Most robust answer so far, though it will lack models provided by packages (think spatie permission/tags etc)
  • RibeiroBreno
    RibeiroBreno almost 4 years
    Thats true @Anders, this code is only concerned about the application classes. :)
  • Szabi Zsoldos
    Szabi Zsoldos over 3 years
    Clever approach, love the idea, didn't know about the is_subclass_of method, this solves my issue.
  • Daniyal Nasir
    Daniyal Nasir about 3 years
    @RibeiroBreno I have traits in one my directory which gives error Class App\Models\Traits\Model does not exist at line $valid = $reflection->isSubclassOf(Model::class) .
  • RibeiroBreno
    RibeiroBreno about 3 years
    Hello @DaniyalNasir! This looks like a namespace issue. Did you include all the "use" statements above?
  • Dmitry Gultyaev
    Dmitry Gultyaev almost 3 years
    @RibeiroBreno, your method is almost perfect, just models could be everywhere, so actually another way to get class list from a folder is to use "symfony/class-loader". This way mapping filename to the correct class name will be done more exact. like: $models = collect(ClassMapGenerator::createMap(base_path($folder)));
  • Christian
    Christian over 2 years
    If the app has service providers that add more models, this answer misses them. (also in cases where app devs decide to use a different path for models)
  • RibeiroBreno
    RibeiroBreno over 2 years
    Nice one @DmitryGultyaev ! This symfony library has been deprecated and moved to composer. See: - github.com/symfony/class-loader/blob/3.4/… - github.com/composer/composer/blob/main/src/Composer/Autoload‌​/…
  • Ali Baba Azimi
    Ali Baba Azimi over 2 years
    Nice and short. Thanks