I am having a hard to figuring out how to refractor a factory in Laravel 7 to Laravel 8. Below is the original factory in L7 and the L8 version below is the one I've tried refactoring. I know the $factory->define is wrong and this is where I am stuck.
Laravel 7 Version
<?php
/** #var \Illuminate\Database\Eloquent\Factory $factory */
use App\Login;
use Faker\Generator as Faker;
$factory->define(Login::class, function (Faker $faker) {
$randomDateTime = $faker->dateTimeBetween('-6 hours', 'now');
return [
'user_id' => factory(App\User::class),
'tenant_id' => factory(App\Tenant::class),
'created_at' => $randomDateTime,
'updated_at' => $randomDateTime,
];
});
Laravel 8 Version
<?php
namespace Database\Factories;
use App\Models\Login;
use App\Models\Tenant;
use App\Models\User;
use Faker\Generator as Faker;
$factory->define(Login::class, function (Faker $faker) {
$randomDateTime = $faker->dateTimeBetween('-6 hours', 'now');
return [
'user_id' => User::factory(),
'tenant_id' => Tenant::factory(),
'created_at' => $randomDateTime,
'updated_at' => $randomDateTime,
];
});
You have two options here:
Use old factories
If you don't have the time for properly refactoring your factories, you can still use your old Laravel 7 factories by pulling in the laravel/legacy-factories package:
composer require laravel/legacy-factories
Refactor your factories
Laravel 8 factories are now classes. Previously, you would call a define method on the $factory object, passing it the class and a closure that would return the factory definition like so:
<?php
use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
];
});
In Laravel 8, the same would be written as follows and be put in database/factories/UserFactory.php for auto-discovery:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
];
}
}
As you see, factories are now separate classes that extend from the Illuminate\Database\Eloquent\Factories\Factory class.
Additionally, you need to use the Illuminate\Database\Eloquent\Factories\HasFactory trait on the Model to allow the discovery of factories for the Model. Once you add this trait, Laravel expects a Factory class by the name of the Model, suffixed with Factory in the database/factories folder.
You can read up on all that in the Defining Model Factories chapter of the database testing documentation.
In your specific case
The code you posted would need to be refactored and put in database/factories/LoginFactory.php:
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class LoginFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
$randomDateTime = $faker->dateTimeBetween('-6 hours', 'now');
return [
'user_id' => User::factory(),
'tenant_id' => Tenant::factory(),
'created_at' => $randomDateTime,
'updated_at' => $randomDateTime,
];
}
}
Then you need to use Illuminate\Database\Eloquent\Factories\HasFactory; in your Login.php model class and of course you would have do the same for the User and Tenant models, creating UserFactory.php and TenantFactory.php.
Related
I have a test in Laravel 7 constructed as follows:
<?php
namespace Tests\Feature\Http\Controllers\Auth;
use App\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class LoginControllerTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic feature test example.
*
* #return void
*/
public function testLoginPractitioner()
{
$user = factory(User::class, 1)->make();
dump($user);
$response = $this->post('/api/login', [
'phone_number' => $user->phone_number,
'password' => $user->password
], [
'Accept' => 'application/json',
'Content_Type' => 'application/json'
]);
$this->assertDatabaseHas('users', [
'phone_number' => $user->phone_number,
]);
}
}
With the user factory defined as this:
$factory->define(User::class, function (Faker $faker) {
return [
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'phone_number' => '12' . $faker->numerify('#########'),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
'is_admin' => false
];
});
When I dump the user object created in the test, I can see that it has a phone_number attribute:
#attributes: array:6 [
"email" => "leonora.tromp#example.com"
"email_verified_at" => "2021-01-31 11:25:02"
"phone_number" => "12326385883"
"password" => "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi"
"remember_token" => "Oy8DfAonMu"
"is_admin" => false
]
But my test keeps failing and I get this message, as if it has not phone_number attribute:
1) Tests\Feature\Http\Controllers\Auth\LoginControllerTest::testLoginPractitioner
Exception: Property [phone_number] does not exist on this collection instance.
Additionally, the assert fails even if I use a number that I am sure is in the database. Why is this happening?
Your problem is $user is a Collection, when you give a factory an amount of models to create, it will return a Collection instance contained the models created in your case 1. Secondly for the model to be saved to the db you should call create() and not make().
Changing the user creation code to the following should solve the problem.
$user = factory(User::class)->create();
If you at a time need to create multiple users, you need to get them out of the Collection. As you seem confused about Collections, probably reading up on Collections would be wise.
I want to pass arguments ['site_id' => $site->id] to SiteMessage factory:
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\SiteMessage;
use App\Models\Site;
class SitesMessagesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Site::chunk(200, function ($sites) {
foreach ($sites as $site) {
SiteMessage::factory()->count(rand(2, 6))->create(['site_id' => $site->id]);
}
});
}
}
How can I get those argument in my SiteMessage factory class?
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\SiteMessage;
use App\Models\Site;
use App\Models\Integration;
class SiteMessageFactory extends Factory
{
protected $model = SiteMessage::class;
public function definition()
{
return [
**// Soliution: remove line below, it will be overridden automaticaly. \\**
'site_id' => $arguments['site_id'], // Neet to use Id that I passed from seeder.
'integration_id'=> Integration::inRandomOrder()->first()->id,
'type' => rand(0,1) ? 'EMAIL' : 'SMS',
'title' => $this->faker->text($maxNbChars = 12),
'description' => $this->faker->sentence,
'message' => $this->faker->sentence,
'enabled' => 1,
'created_at' => now(),
'updated_at' => now(),
];
}
}
At older Laravel factory version I could pass them in callback like so:
$factory->define(SiteMessage::class, function (Faker $faker, array $arguments = []) {
//
});
but don't know how to achieve it with new Class factories. Any help would be very appreciated :)
As you can see in the laravel documentation about persisting models with factories, when you type:
SiteMessage::factory()->count(rand(2, 6))->create(['site_id' => $site->id]);
The site_id attribute from SiteMessage factory will be overrided by the $site->id you are specifying.
I am trying to insert static data, with the help of the seeder and Eloquent as bellow.
use Illuminate\Database\Seeder;
use App\Role;
class RoleSeeder extends Seeder
{
public function run()
{
Role::insert(array(
array('name' => 'admin'),
array('name' => 'user')
));
}
}
Even after using Eloquent i am getting timestamp null in database.
The timestamp columns (created_at and updated_at) will be assigned automatically only if you are using the Eloquent
save() method and create method are Eloquent.
While insert method is not Eloquent, it's a query builder method. So use create method instead :
public function run()
{
Role::create(array(
array('name' => 'admin'),
array('name' => 'user')
));
}
When you are using query builder, you have to created_at and updated_at value by yourself :
Role::insert(array(
array('name' => 'admin'),
array('name' => 'user'),
array('created_at' => \Carbon\Carbon::now()),
array('updated_at' => \Carbon\Carbon::now()),
));
Try this instead
<?php
use Illuminate\Database\Seeder;
use App\Role;
class RoleSeeder extends Seeder
{
public function run()
{
Role::truncate();
Role::create(['name' => 'admin']);
Role::create(['name' => 'user']);
}
}
The error is generated because you are using insert instead of create.
I want to generate fake data for user with diffrent role ,
My file is below,
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Faker\Factory as Faker;
class InsertUserSeeder extends Seeder {
/**
* Run the database seeds.
*
* #return void
*/
public function run() {
$faker = Faker::create();
foreach (range(1, 10) as $index) {
DB::table('user')->insert([
'name' => $faker->name,
'email' => $faker->email,
'role' => 'admin',
]);
}
}
}
From above code it will generate 10 records of admin role.
I want to generate records with different role from['admin','superadmin','client','agent','engineer']
how can I fetch value for role from given array of roles.
You can use the method randomElement($array) of the faker to do the job like so:
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Faker\Factory as Faker;
class InsertUserSeeder extends Seeder {
/**
* Run the database seeds.
*
* #return void
*/
public function run() {
$faker = Faker::create();
foreach (range(1, 10) as $index) {
DB::table('user')->insert([
'name' => $faker->name,
'email' => $faker->email,
'role' => $faker->randomElement([
'admin',
'superadmin',
'client',
'agent',
'engineer',
]),
]);
}
}
}
For reusability, I would suggest using model factories instead of creating the models directly in the seeder. You can read up on it in the documentation.
How can I seed multiple rows using ModelFactory in Laravel?
Inside ModelFactory.php I have the following code:
$factory->define(App\User::class, function (Faker $faker) {
static $password;
return [
'name' => 'Admin',
'Description' => 'Administrators have full access to everything.'
];
});
How can I add the following arrays, without using raw expressions?
[
'name' => 'Admin',
'description' => 'Administrators have full access to everything.',
],
[
'name' => 'User',
'description' => 'User have normal access.',
],
Thanks
You can use sequence()
User::factory()->count(2)->sequence(['name' => 'admin'],['name' => 'user'])
->create()
example from laravel documentation
$users = User::factory()
->count(10)
->sequence(fn ($sequence) => ['name' => 'Name '.$sequence->index])
->create();
source https://laravel.com/docs/8.x/database-testing#sequences
Let's say you want to add 100 users in your database.
Create a UserFactory.php in database/factories:
<?php
use Faker\Generator as Faker;
$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => 'Admin',
'Description' => 'Administrators have full access to everything.'
];
});
Then, in database/seeds/DatabaseSeeder.php:
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
factory(App\User::class, 100)->create();
}
}
You can find more details about seeding in the Laravel official documentation.
If you want to have one or two columns to have custom data, you can use each function after creating entries.
$names = ['admin', 'user', 'author', 'subscriber'];
factory(App\User::class, 100)->create()->each(function () use ($names) {
$user->name = $names[array_rand($names)];
$user->save();
});
*Note: Use your own logic inside each function to feed the custom data.
A cleaner way than Raghavendra's proposal (creates one entry per name):
$names = ['admin', 'user', 'author', 'subscriber'];
collect($names)->each(function($name) {
App\Models\User::factory()->create($name);
});