Database Factory
Introduction
Models can use factories to automatically generate data for your application. They're incredibly useful to get 'real' data in place without the bloat of a database dump.
Out of the box, a model does not need a factory defined. It will fallback to the core factory for the respective model type. For example, a post model will fallback to the Mantle default post factory. However, if you want to define a factory for a model, you can do so. See Defining Custom Factory for a Model for more information.
Creating Models Using Factories
You can use the factory()
method on a model to retrieve the factory for the
respective model and start creating models.
use App\Models\Post;
Post::factory()->create(); // int
Factories can also be used statically within a Mantle Teskit test case:
use Mantle\Testkit\Test_Case as Testkit;
class ExampleTest extends Testkit {
public function test_factory() {
$post = static::factory()->post->create_and_get(); // WP_Post.
$term_id = static::factory()->tag->create(); // int
// ...
}
}
Create a Single Instance
Create a model from the factory definition and optionally override it with your
own arguments with create()
. Returns the ID of the created model.
- Framework Use
- Testkit Use
use App\Models\Post;
Post::factory()->create(); // int
Post::factory()->create(
[
'post_title' => 'My Custom Title',
]
);
use App\Models\Post;
use Mantle\Testkit\Test_Case as Testkit;
class ExampleTest extends Testkit {
public function test_factory() {
$post = static::factory()->post->create_and_get(); // WP_Post.
$term_id = static::factory()->tag->create(); // int
// ...
}
}
Create Multiple Instances
Create multiple models from the factory definition and optionally override it
with your own arguments using the count()
or create_many()
methods. It will return
an array of IDs of the created models.
- Framework Use
- Testkit Use
use App\Models\Post;
Post::factory()->count( 3 )->create(); // int[]
Post::factory()->count( 3 )->create( [
'post_title' => 'My Custom Title',
] );
$instance = Post::factory()->count( 3 )->create_and_get(); // App\Models\Post[]
use App\Models\Post;
use Mantle\Testkit\Test_Case as Testkit;
class ExampleTest extends Testkit {
public function test_factory() {
$post_ids = static::factory()->post->create_many( 3 ); // int[]
$term_ids = static::factory()->tag->create_many( 3 ); // int[]
// You may use the fluent count() method to create multiple models, too.
$post_ids = static::factory()->post->count( 3 )->create(); // int[]
$term_ids = static::factory()->tag->count( 3 )->create(); // int[]
// ...
}
}
Create and Get a Model Instance
Create a model from the factory definition and optionally override it with your
own arguments using create_and_get()
. It will return the created model/core
object or an array of created models if chained with count()
:
- Framework Use
- Testkit Use
use App\Models\Post;
Post::factory()->create_and_get(); // \App\Models\Post
Post::factory()->create_and_get(
[
'post_title' => 'My Custom Title',
]
);
use App\Models\Post;
use Mantle\Testkit\Test_Case as Testkit;
class ExampleTest extends Testkit {
public function test_factory() {
$post = static::factory()->post->create_and_get(); // WP_Post
$term = static::factory()->tag->create_and_get(); // WP_Term
// count() can be used to create multiple models.
$posts = static::factory()->post->count( 3 )->create_and_get(); // WP_Post[]
$terms = static::factory()->tag->count( 3 )->create_and_get(); // WP_Term[]
// ...
}
}
You can also retrieve a model instance instead of WP_Post/WP_Term by chaining
the as_models()
method to the create_and_get()
method:
use App\Models\Post;
use Mantle\Testkit\Test_Case as Testkit;
class ExampleTest extends Testkit {
public function test_factory() {
$post = static::factory()->post->as_models()->create_and_get(); // Mantle\Database\Model\Post
$term = static::factory()->tag->as_models()->create_and_get(); // Mantle\Database\Model\Term
// ...
}
}
create_and_get()
depends on how the factory is usedcreate_and_get()
will return either the model instance or the underlying "core
object" (WP_Post/WP_User/WP_Term/etc.) depending on the configuration of the
factory. When a factory is used via the Model::factory()
method,
create_and_get()
will return the model instance. When a factory is used via
the static::factory()->post
method in unit tests, create_and_get()
will
return the underlying "core object" (WP_Post/WP_User/WP_Term/etc.).
Defining Custom Factory for a Model
Out of the box, any model that does not have a factory defined will fallback to the core factory for the respective model type.
Model Type | Core Factory |
---|---|
Attachment | Mantle\Database\Factory\Attachment_Factory |
Site | Mantle\Database\Factory\Blog_Factory |
Comment | Mantle\Database\Factory\Comment_Factory |
Network | Mantle\Database\Factory\Network_Factory |
Post | Mantle\Database\Factory\Post_Factory |
Term | Mantle\Database\Factory\Term_Factory |
User | Mantle\Database\Factory\User_Factory |
These factories provide a basic definition for the model. If you want to define a custom factory for a model, you can do so by creating a custom factory for your model.
Generating a Factory
You can create a factory by adding a new file in the
database/factory
folder. The expected factory name is:
App\Database\Factory\{Model_Name}_Factory
This can be generated using the make:factory
command:
bin/mantle make:factory {name} {--model_type=} {--object_name=}
Using Custom Model Factory
Once a factory is generated, you can modify the definition()
method of the
factory to define the data that should be generated. The definition()
method
should return an array of data that will be used to create the model.
namespace App\Database\Factory;
use App\Models\Example_Post_Model;
/**
* Example Post Model Factory
*
* @extends \Mantle\Database\Factory\Post_Factory<\App\Models\Example_Post_Model>
*/
class Example_Post_Model_Factory extends \Mantle\Database\Factory\Post_Factory {
/**
* Model to use when creating objects.
*
* @var class-string
*/
protected string $model = Example_Post_Model::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition(): array {
return [
'post_title' => $this->faker->sentence,
'post_content' => $this->faker->paragraph,
'post_status' => 'publish',
'post_type' => 'post',
];
}
}
Generating Blocks in Factories
All factories have an instance of Faker available
to them on the $this->faker
property. This can be used to generate blocks
using the block()
method. You can optionally include attributes and content.
$this->faker->block(
'namespace/block',
'The Content',
[
'exampleAttr' => true,
'another' => false,
]
);
Which would produce this:
<!-- wp:namespace/block {"exampleAttr":true,"another":false} -->
The Content
<!-- /wp:namespace/block -->
Faker also supports generating paragraph blocks using the
paragraph_block()
/paragraph_blocks()
methods.
$this->faker->paragraph_block( int $sentences = 3 ): string
$this->faker->paragraph_blocks( int $count = 3, bool $as_text = true ): string|array
Factory States
You can define states for a factory by defining a method on the factory and use
the state()
method to define the state. The state()
method accepts an array
of data to use when creating the model.
use Mantle\Database\Factory\Post_Factory;
public function my_custom_state(): Post_Factory {
return $this->state(
[
'post_title' => 'My Custom Title',
]
);
}
You can then use the state when creating a model:
$example_post = Example_Post_Model::factory()->my_custom_state()->create();
Generating Content
See Creating Models Using Factories for more
information about the create()
/create_many()
/create_and_get()
methods.
- Model Factory
- Testkit Factory
use App\Models\Post;
use App\Models\Category;
$post_id = Post::factory()
->with_meta( [ 'key' => 'value' ] )
->create();
$term_id = Category::factory()->tag
->with_meta( [ 'key' => 'value' ] )
->create();
// ...
use App\Models\Post;
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$post_id = static::factory()->post
->with_meta( [ 'key' => 'value' ] )
->create();
// ...
$term_id = static::factory()->tag->with_meta(
[
'key' => 'value',
]
)->create();
// ...
}
}
Generating Posts/Terms with Meta
Posts and terms can be generated with meta by calling the with_meta
method on
the factory:
- Model Factory
- Testkit Factory
use App\Models\Post;
use App\Models\Category;
$post_id = Post::factory()
->with_meta( [ 'key' => 'value' ] )
->create();
$term_id = Category::factory()->tag
->with_meta( [ 'key' => 'value' ] )
->create();
// ...
use App\Models\Post;
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$post_id = static::factory()->post
->with_meta( [ 'key' => 'value' ] )
->create();
// ...
$term_id = static::factory()->tag->with_meta(
[
'key' => 'value',
]
)->create();
// ...
}
}
Generating Posts with Terms
Posts can be generated with terms by calling the with_terms()
method on the
factory:
- Model Factory
- Testkit Factory
use App\Models\Post;
use App\Models\Tag;
$tag_id = Tag::factory()->create();
$post_id = Post::factory()->with_terms(
// Pass in taxonomy => slug pairs
[
'category' => [
'category_a',
'category_b',
],
],
// Pass in a single taxonomy => slug pair.
[
'post_tag' => 'single-tag',
],
// Pass in term ID(s).
$tag_id,
// Or pass in term objects.
Tag::factory()->create_and_get(),
)->create();
// ...
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$tag_id = static::factory()->tag->create();
$post_id = static::factory()->post->with_terms(
// Pass in taxonomy => slug pairs
[
'category' => [
'category_a',
'category_b',
],
],
// Pass in a single taxonomy => slug pair.
[
'post_tag' => 'single-tag',
],
// Pass in term ID(s).
$tag_id,
// Or pass in term objects.
static::factory()->tag->create_and_get(),
)->create();
// ...
}
}
Generating Terms with Posts
Terms can be generated with posts by calling the with_posts()
method on the
term factory:
- Model Factory
- Testkit Factory
use App\Models\Post;
use App\Models\Tag;
$post_id = Post::factory()->create();
$term_id = Tag::factory()->with_posts(
// Pass in post ID(s).
$post_id,
// Or pass in post objects.
Post::factory()->create_and_get(),
)->create();
// ...
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$post_id = static::factory()->post->create();
$term_id = static::factory()->tag->with_posts(
// Pass in post ID(s).
$post_id,
// Or pass in post objects.
static::factory()->post->create_and_get(),
)->create();
// ...
}
}
Generating Post with Thumbnail
Posts can be generated with a thumbnail by calling the with_thumbnail
method
on the post factory:
- Model Factory
- Testkit Factory
use App\Models\Post;
Post::factory()->with_thumbnail()->create_and_get();
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$post = static::factory()->post->with_thumbnail()->create_and_get();
// ...
}
}
The underlying post will have an attachment set as the thumbnail (via the
_thumbnail_id
post meta). The attachment will not have a real image file
attached to it for performance. If you'd like a real underlying image file, you
can use the with_real_thumbnail()
method:
- Model Factory
- Testkit Factory
use App\Models\Post;
Post::factory()->with_real_thumbnail()->create_and_get();
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$post = static::factory()->post->with_real_thumbnail()->create_and_get();
// ...
}
}
See Generating an Attachment with a Real Image File for more information.
Generating Posts with a Custom Post Type
Posts can be generated with a custom post type by calling the with_post_type()
method on the factory:
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$post_id = static::factory()->post->with_post_type( 'custom_post_type' )->create();
}
}
Generating an Ordered Set of Posts
Mantle includes a helper to create an ordered set of posts that are evenly spaced out. This can be useful when trying to populate a page with a set of posts and want to verify the order of the posts on the page.
- Model Factory
- Testkit Factory
use App\Models\Post;
$post_ids = Post::factory()->create_ordered_set( 10 );
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_create_ordered_set() {
$post_ids = static::factory()->post->create_ordered_set( 10 );
}
}
The above post IDs are evenly spaced an hour apart starting from a month ago. The start date and the separation can also be adjusted:
use Carbon\Carbon;
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_create_ordered_set() {
$post_ids = static::factory()->post->create_ordered_set(
10,
[],
// Start creating the post a year ago.
Carbon::now()->subYear(),
// Spread them out by a day.
DAY_IN_SECONDS,
);
}
}
Generating an Attachment with a Real Image File
By default, all attachments generated by the factory won't have a "real" file
included with it for performance. You can opt to create an attachment with a
real image file by calling the with_image()
method on the attachment factory:
- Model Factory
- Testkit Factory
use App\Models\Attachment;
Attachment::factory()->with_image()->create_and_get();
You can also pass a local file to use as the image:
use App\Models\Attachment;
Attachment::factory()->with_image(
__DIR__ . '/image.jpg'
)->create_and_get();
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$attachment_id = static::factory()->attachment->with_image()->create();
// ...
}
}
You can also pass a local file to use as the image:
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
public function test_factory() {
$attachment_id = static::factory()->attachment->with_image(
__DIR__ . '/image.jpg'
)->create();
// ...
}
}
Factory Middleware
Mantle includes a middleware system that allows you to hook into the factory process and modify the data before it's saved to the database. This can be useful if you want to modify the data before it's saved or if you want to perform some action after the data is saved. The factory itself uses middleware to assign terms to posts after they're saved, set meta, and more.
Middleware can be added to the factory by calling the with_middleware()
method:
use Mantle\Testkit\Test_Case as Testkit_Test_Case;
class Test_Case extends Testkit_Test_Case {
protected $custom_factory;
protected function setUp(): void {
parent::setUp();
$this->custom_factory = static::factory()->post->with_middleware(
function ( array $args, \Closure $next ) {
// Modify the arguments (if needed).
// Call the next middleware
$result = $next( $args );
// Modify the result (if needed).
return $result;
}
}
}