laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.32k stars 10.95k forks source link

Calling a model factory within another factory breaks in a namespaced package #45635

Closed cannycookie closed 1 year ago

cannycookie commented 1 year ago

Steps To Reproduce:

In a package create a Factory and call another factory from within it.

namespace Visualbuilder\Core\Database\Factories;

use Visualbuilder\Core\Models\Form;
use Visualbuilder\Core\Models\Layout;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends Factory<\Visualbuilder\Core\Models\Form>
 */
final class FormFactory extends Factory
{

    protected $model = Form::class;

    public function definition(): array
    {

        return [
            'name'      => $this->faker->name,
            'layout_id' => Layout::factory()
        ];
    }
}

Run the seeder and this error is thrown:-

Class "App\Layout" not found

The namespace is lost due to a borked function modelName() in:-

src/Illuminate/Database/Eloquent/Factories/Factory.php

propose changing:-

 public function modelName()
    {
        $resolver = static::$modelNameResolver ?? function (self $factory) {
            $namespacedFactoryBasename = Str::replaceLast(
                'Factory', '', Str::replaceFirst(static::$namespace, '', get_class($factory))
            );

            $factoryBasename = Str::replaceLast('Factory', '', class_basename($factory));

            $appNamespace = static::appNamespace();

            return class_exists($appNamespace.'Models\\'.$namespacedFactoryBasename)
                ? $appNamespace.'Models\\'.$namespacedFactoryBasename
                : $appNamespace.$factoryBasename;
        };

        return $this->model ?? $resolver($this);
    }

to

 public function modelName()
   {
       $resolver = static::$modelNameResolver ?? function (self $factory) {
            $namespacedFactoryBasename = Str::replaceLast(
                'Factory', '', Str::replaceFirst(static::$namespace, 'Models\\', get_class($factory))
            );

            $factoryBasename = Str::replaceLast('Factory', '', class_basename($factory));

            $appNamespace = static::appNamespace();

            return class_exists($namespacedFactoryBasename)
                ? $namespacedFactoryBasename
                : $appNamespace.$factoryBasename;
        };

        return $this->model ?? $resolver($this);
}
driesvints commented 1 year ago

Heya, thanks for reporting.

We'll need more info and/or code to debug this further. Can you please create a repository with the command below, commit the code that reproduces the issue as one separate commit on the main/master branch and share the repository here? Please make sure that you have the latest version of the Laravel installer in order to run this command. Please also make sure you have both Git & the GitHub CLI tool properly set up.

laravel new bug-report --github="--public"

Please do not amend and create a separate commit with your custom changes. After you've posted the repository, we'll try to reproduce the issue.

Thanks!

cannycookie commented 1 year ago

Made a new package as simple as can be and as a result, found the bug was user error 😳😳

The factory was missing the model definition.

protected $model = Layout::class;

Urgh, wasted hours and it was the simplest of things. Case closed.