{"record":{"id":"9378bbf9ccb44c46","repo":"flarum/framework","slug":"modelclass-must-use-the-hasfactory-trait-and-have-a-factory","errorCode":null,"errorMessage":"$modelClass must use the HasFactory trait and have a Factory class.","messagePattern":"\\$modelClass must use the HasFactory trait and have a Factory class\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"php-packages/testing/src/integration/TestCase.php","lineNumber":275,"sourceCode":"        if ($this->database()->getDriverName() === 'pgsql') {\n            // PgSQL doesn't auto-increment the sequence when inserting the IDs manually.\n            foreach ($tables as $table => $id) {\n                $wrappedTable = $this->database()->getSchemaGrammar()->wrapTable($table);\n                $seq = $this->database()->getSchemaGrammar()->wrapTable($table.'_'.$id.'_seq');\n                $this->database()->statement(\"SELECT setval('$seq', (SELECT MAX($id) FROM $wrappedTable))\");\n            }\n\n            $this->database()->statement(\"SET session_replication_role = 'origin'\");\n        }\n\n        // And finally, turn on foreign key checks again.\n        $this->database()->getSchemaBuilder()->enableForeignKeyConstraints();\n    }\n\n    protected function throughFactory(string $modelClass, array $attributes): array\n    {\n        if (! method_exists($modelClass, 'factory')) {\n            throw new RuntimeException(\"$modelClass must use the HasFactory trait and have a Factory class.\");\n        }\n\n        /** @var \\Illuminate\\Database\\Eloquent\\Factories\\Factory $factory */\n        $factory = $modelClass::factory();\n\n        return array_map(function (mixed $value) {\n            return is_array($value) ? json_encode($value) : $value;\n        }, $factory->raw($attributes));\n    }\n\n    protected function rowsThroughFactory(string $modelClass, array $rows): array\n    {\n        return array_map(function (array $row) use ($modelClass) {\n            return $this->throughFactory($modelClass, $row);\n        }, $rows);\n    }\n\n    /**","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/flarum/framework/blob/4b939f685389bfe8a380e9e28ddf305a1c66950c/php-packages/testing/src/integration/TestCase.php#L257-L293","documentation":"Flarum's integration testing TestCase builds test rows through Eloquent factories via throughFactory(); if the given model class has no `factory` method it throws a RuntimeException stating the model must use the HasFactory trait and have a Factory class. The testing helpers assume every model used in test fixtures is factory-backed. This is a developer-facing setup error, not a runtime data problem.","triggerScenarios":"Using the TestCase's database row helpers (e.g. rowsThroughFactory / prepareDatabase with a model name) with a model class that lacks the HasFactory trait or whose factory class doesn't exist, so `$modelClass::factory()` / method_exists($modelClass, 'factory') fails.","commonSituations":"Custom extension models created without a corresponding Factory class; forgetting `use HasFactory` when overriding Laravel conventions; passing a non-Eloquent class or wrong class-string to the testing helper.","solutions":["Add the HasFactory trait to the model and create a matching Factory class (or set the $factory class property / newFactory() override for non-standard namespaces)","Point the test helper at an existing factory-backed model instead","For untestable models, seed rows with plain arrays/DB inserts instead of the factory path"],"exampleFix":"// before\nclass Discussion extends Model {} // no factory\n// after\nuse Illuminate\\Database\\Eloquent\\Factories\\HasFactory;\nclass Discussion extends Model { use HasFactory; }\n// plus\nclass DiscussionFactory extends Factory { protected $model = Discussion::class; public function definition() { return [/* ... */]; } }","handlingStrategy":"validation","validationCode":"if (!method_exists($modelClass, 'factory')) { throw new \\LogicException(\"$modelClass needs HasFactory + a Factory class before use in tests\"); }","typeGuard":null,"tryCatchPattern":"try { $this->prepareDatabase([...]); } catch (RuntimeException $e) { $this->fail('Add HasFactory/Factory to model: '.$e->getMessage()); }","preventionTips":["Give every extension model a Factory class from the start","Use `php artisan make:factory` when creating models","Verify new models compile by running a trivial factory-based test"],"tags":["testing","eloquent","factories","phpunit"],"backgroundTag":"missing-dependency","analyzedSha":"4b939f685389bfe8a380e9e28ddf305a1c66950c","analyzedAt":"2026-09-15T18:09:20.879Z","contentChangedAt":"2026-09-15T18:09:20.879Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}