public function findAll($paths) : array
    {
        if (is_string($paths)) {
            $paths = [$paths];
        }

        if (!is_array($paths)) {
            throw new InvalidArgumentException('Paths must be a string or array');
        }

        $allClassNames = [];

        foreach ($paths as $path) {
            if (!is_dir($path)) {
                throw new InvalidArgumentException("Path $path is not a directory");
            }

            $fileIter = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));

            foreach ($fileIter as $file) {
                if ($file->getExtension() !== 'php') {
                    continue;
                }

                $tokens = token_get_all(file_get_contents($file->getRealPath()));
                $allClassNames = array_merge($allClassNames, $this->getClassNamesFromTokens($tokens));
            }
        }

        // Filter out any non-concrete migration classes
        $migrationClassNames = array_filter($allClassNames, function ($className) {
            $reflectionClass = new ReflectionClass($className);

            return $reflectionClass->implementsInterface(IMigration::class) &&
                !$reflectionClass->isInterface() &&
                !$reflectionClass->isAbstract();
        });

        // Sort the migration classes by their creation dates
        usort($migrationClassNames, function (string $a, string $b) {
            return $a::getCreationDate() <=> $b::getCreationDate();
        });

        return $migrationClassNames;
    }