Laravel access relationship models in factories - php

I'm working inside a Laravel 9 project and am using model factories. I have User which can have one Company.
I need the CompanyFactory details to be linked to the User, such as the first name and last name. The user_id is already mapped with Laravel.
This is my attempt, or what I thought I could do within the CompanyFactory:
$this->user->first_name
Which is undefined?
Here's my seeder:
// development seeders
User::factory(2)
->has(Affiliate::factory()->count(50))
->has(Company::factory()->count(1))
->has(Country::factory()->count(3))
->create();
And my CompanyFactory
<?php
namespace Database\Factories;
use App\Models\User;
use App\Models\Country;
use App\Models\Company;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Carbon\Carbon;
class CompanyFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Company::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
Log::debug('user', [
'user' => $this->user,
]);
return [
'contact_first_name' => $user->first_name,
'contact_last_name' => $user->last_name ? $user->last_name : null,
'company_name' => $this->faker->company(),
'address_1' => $this->faker->numberBetween(1, 16),
'address_2' => 'Heatherbell Cottages',
'address_3' => null,
'town' => 'Wick',
'county' => 'Caithness',
'postcode' => 'KW14YT',
'telephone_1' => $this->faker->regexify('07[1-57-9]{1}[0-9]{8}'),
'telephone_2' => $this->faker->regexify('07[1-57-9]{1}[0-9]{8}'),
'email' => $user->email,
'bank_name' => $this->faker->word(),
'bank_account_number' => $this->faker->numberBetween(11111111, 99999999),
'bank_sort_code' => $this->faker->numberBetween(111111, 999999),
'bank_iban' => $country ? $this->faker->iban($country->country_code) : null,
'bank_swift' => '',
'ccl_number' => null,
'data_protection_number' => $this->faker->numberBetween(11111111, 99999999),
'currency' => $country ? $country->currency_code : 'GBP',
'notes' => ''
];
}
}

You might have a belongsTo relation in the Company model.
You can use code like this.
$company = Company::factory()
->for(User::factory()->state([
'name' => 'User name',
]))
->create();
OR
$user = User::factory()->create();
$company = Company::factory()
->for($user)
->create();
https://laravel.com/docs/9.x/eloquent-factories#belongs-to-relationships

Related

What causes the 'Unknown format "factory"' error in this Laravel 8 app?

I am working on a Laravel 8 app with users and posts.
The objective is to create a bunch of posts (I already have users).
namespace Database\Factories;
// import Post model
use App\Models\Post;
// import User model
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory {
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Post::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition() {
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => $this->faker->factory(App\Models\User::class),
];
}
}
The problem
I run php artisan tinker then Post::factory()->count(100)->create() in the terminal and I get:
InvalidArgumentException with message 'Unknown format "factory"'
UPDATE
I replace my return statement with:
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
I get this in the terminal:
Class 'Database\Factories\UserFactory' not found
Questions:
Where is my mistake?
Does the fact that I get the error Class 'Database\Factories\UserFactory' not found mean that I need to
create a UserFactory factory? Because there isn't one. (I wanted
to create posts, not users).
I don't suppose there is $this->faker->factory(..).
You can do
'user_id' => App\Models\User::factory()->create()->id,
EDIT:
'user_id' => App\Models\User::factory(),
Creating a UserFactory factory and using the below return statement did the trick:
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
So, the PostFactory class looks like this:
class PostFactory extends Factory {
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Post::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition() {
return [
'title' => $this->faker->sentence(3),
'description' => $this->faker->text,
'content' => $this->faker->paragraph,
'user_id' => User::factory(),
];
}
}

Save user's different type data in different tables while updating userprofile in jetstream update form

I added two more tables Business and Bank along with User in jetstream registration form. I inserted data successfully in all these three tables at once at registration process.
my app/Actions/Fortify/CreatedNewUser.php file is:
<?php
namespace App\Actions\Fortify;
use App\Models\Team;
use App\Models\User;
use App\Models\Business;
use App\Models\Bank;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\CreatesNewUsers;
use Laravel\Jetstream\Jetstream;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
/**
* Create a newly registered user.
*
* #param array $input
* #return \App\Models\User
*/
public function create(array $input){
$user = User::create([
'user_id' => $input['user_id'],
'username' => $input['username'],
'name' => $input['name'],
'lname' => $input['lname'],
'email' => $input['email'],
'phone' => $input['phone'],
'cell' => $input['cell'],
'persnl_add1' => $input['persnl_add1'],
'persnl_add2' => $input['persnl_add2'],
'city' => $input['city'],
'province' => $input['province'],
'country' => $input['country'],
'web' => $input['web'],
'password' => Hash::make($input['password']),
]);
Business::create([
'user_id' => $input['user_id'],
'bsns_name' => $input['bsns_name'],
'bsns_add1' => $input['bsns_add1'],
'bsns_add2' => $input['bsns_add2'],
'bsns_city' => $input['bsns_city'],
'bsns_province' => $input['bsns_province'],
'bsns_country' => $input['bsns_country'],
]);
Bank::create([
'user_id' => $input['user_id'],
'branch_code' => $input['branch_code'],
'bank_name' => $input['bank_name'],
'acc_no' => $input['acc_no'],
'acc_title' => $input['acc_title'],
]);
return $user;
}
}
Here I inserted data in all three tables but now I want to put this user data in update form to update. I have no idea where to update data of Business and Bank tables too along with User table.
here is the app/Actions/Fortify/UpdateUserProfileInformation.php
<?php
namespace App\Actions\Fortify;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
use Illuminate\Support\Facades\Hash;
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
{
/**
* Validate and update the given user's profile information.
*
* #param mixed $user
* #param array $input
* #return void
*/
public function update($user, array $input)
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'],
])->validateWithBag('updateProfileInformation');
if (isset($input['photo'])) {
$user->updateProfilePhoto($input['photo']);
}
if ($input['email'] !== $user->email &&
$user instanceof MustVerifyEmail) {
$this->updateVerifiedUser($user, $input);
} else {
$user->forceFill([
'username' => $input['username'],
'name' => $input['name'],
'lname' => $input['lname'],
'email' => $input['email'],
'phone' => $input['phone'],
'cell' => $input['cell'],
'persnl_add1' => $input['persnl_add1'],
'persnl_add2' => $input['persnl_add2'],
'city' => $input['city'],
'province' => $input['province'],
'country' => $input['country'],
'web' => $input['web'],
])->save();
}
}
/**
* Update the given verified user's profile information.
*
* #param mixed $user
* #param array $input
* #return void
*/
protected function updateVerifiedUser($user, array $input)
{
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
'email_verified_at' => null,
])->save();
$user->sendEmailVerificationNotification();
}
}
Here I can't see to where update data of those other tables
well , you can do like this:
public function update($user, array $input)
{
// your function is fine ,dont change any thing but add this lines:
//find your model objects;
$business = Business::find([user_id=>$user->user_id]);
$bank= Bank::find([user_id=>$user->user_id]);
// start adding data to your first model:
$business->bsns_name = $input['bsns_name'],
$business->bsns_add1= $input['bsns_add1'],
.
.
.
$business->save();
// and do this with your $bank object too;
$bank->bank_name= $input['bank_name'],
.
.
.
$bank->save();
}
In User model
protected $appends = ['bsns_name']; # add every field you want to access
.....
#create attribute
protected function getBsnsNameAttribute()
{
return $this->business->bsns_name;
}
If you want to access some field from another Model, create an attribute in User modal that gives the data you want for that user.
For example:
'business' I suppose is your relation to 'Business' model with 'User' model.
Then in your 'update-profile-information-form.blade.php' you can access to that attribute.
<div class="col-span-6 sm:col-span-4">
<x-jet-label for="bsns_name" value="{{ __('Bsns Name') }}" />
<x-jet-input id="bsns_name" type="text" class="mt-1 block w-full" wire:model.defer="state.bsns_name" autocomplete="bsns_name" />
<x-jet-input-error for="bsns_name" class="mt-2" />
</div>
Apply this for every field that you need.

Tests in Laravel not picking up collection attributes

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.

Idgenerator laravel 8, how to fix Exception "table field type is bigint but prefix is string"

why does this message always appear?
even though I want to make an id with 2 dots like = 190772.2021.00000001
can't add '.' (dots) like that can only add 1 dots only 1907722021.00000001
factory.php
namespace Database\Factories;
use App\Models\Anggota;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\User;
use Haruncpi\LaravelIdGenerator\IdGenerator;
class AnggotaFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Anggota::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
$tgl = $this->faker->date;
return [
'id_user' => User::factory(),
'no_anggota' => IdGenerator::generate([
'table' => 'anggotas',
'length' => 19,
'prefix' => date('dmy', strtotime($tgl)).date('.Y.')
]),
'nama_lengkap' => $this->faker->name,
'tempat_lahir' => $this->faker->cityPrefix,
'tanggal_lahir' => $tgl,
'nama_instansi' => $this->faker->word,
];
}
}
file migration
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAnggotasTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('anggotas', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('id_user')->unique();
$table->string('no_anggota');
$table->string('nama_lengkap');
$table->string('tempat_lahir');
$table->date('tanggal_lahir');
$table->string('nama_instansi');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('anggotas');
}
}
whereas column "no_anggota" in the schema database is of type "string" but appears "table field type is bigint but prefix is string"
how to solve it so that the result = 190772.2021.00000001
help me, thanks
The laravel ID generator package by default use table's id column. According to your need, the documentation says (example 4), you have to pass field also.
Your required output: 190772.2021.00000001 ( length 20 with two dots)
Solution: Just pass 'field' => 'no_anggota' and 'length' => 20
$tgl = $this->faker->date;
$idConfig = [
'table' => 'anggotas',
'field' => 'no_anggota',
'length' => 20,
'prefix' => date('dmy', strtotime($tgl)).date('.Y.')
];
return [
'id_user' => User::factory(),
'no_anggota' => IdGenerator::generate($idConfig),
'nama_lengkap' => $this->faker->name,
'tempat_lahir' => $this->faker->cityPrefix,
'tanggal_lahir' => $tgl,
'nama_instansi' => $this->faker->word,
];

Seed data with relationship in Laravel

I'm trying to seed my Laravel 5.6 application through faker factory, I went through the link and little bit confused, As I have some basic static data, like for example I've a company model:
class Company extends Model {
use SoftDeletes, HasDataSearchTable, HasSlug;
protected $fillable = [
'name', 'code_link', 'slug', 'establishment', 'parent_id', 'website', 'updates', 'user_id', 'tracked', 'verified', 'active', 'premium', 'status'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'created_at','updated_at','deleted_at'
];
public function roles()
{
return $this->belongsToMany('Noetic\Plugins\Conxn\Models\Variables\Company\Role', 'company_role_relation', 'company_id', 'role_id')->withTimestamps();
}
}
And a relational role model:
class Role extends Model
{
use SoftDeletes , HasDataSearchTable;
protected $table='company_role';
protected $fillable = [
'name', 'parent_id'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'created_at','updated_at','deleted_at'
];
}
and respective database, I'm following the laravel convention, Now I want to seed the data:
I've particular set of roles which I'm seed in manually,
class CompanyRoleSeed extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
DB::table('company_role')->insert([
['name' => 'Contractor', 'parent_id' => null],
['name' => 'Consultant', 'parent_id' => null],
['name' => 'Manufacturer', 'parent_id' => null],
['name' => 'Miscellaneous', 'parent_id' => null],
['name' => 'Owner', 'parent_id' => null],
['name' => 'Supplier', 'parent_id' => null],
]);
}
}
For company I want to create factory so I did:
$factory->define(Company::class, function (Faker $faker) {
return [
'name' => $faker->company,
'code_link' => rand(5, 10),
'slug' => str_slug($faker->company),
'about' => $faker->paragraphs(),
'establishment' => $faker->randomElement('2015', '2016', '2017', '2018'),
'parent_id' => $faker->randomElement(null, '1', '2', '3'),
'website' => $faker->url,
'user_id' => $faker->randomElement('1', '2', '3', '4', '5'),
'updates' => $faker->paragraphs(),
'tracked' => $faker->boolean,
'verified' => $faker->boolean,
'active' => $faker->boolean,
'premium' => $faker->boolean,
'status' => $faker->randomElement('saved', 'draft')
];
});
And in company seed I'm having:
class CompanySeed extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
factory(Company::class, 10)->create()->each(function ($company) {
$company->roles()->save(); // Don't now how to execute here
});
}
}
Help me at place $company->roles()->save(); What should I do over here.
Any guidance or improvisation welcome.
You can query which roles you want to assign to the companies and related them to the created records like this:
class CompanySeed extends Seeder
{
public function run()
{
$contractorRole = Role::whereName('Contractor')->firstOrFail();
$ownerRole = Role::whereName('Owner')->firstOrFail();
factory(Company::class, 10)->create()->each(function ($company) use ($contractorRole, $ownerRole) {
$company->roles()->attach([
$contractorRole->id,
$ownerRole->id
]);
});
}
}
You can check the doc for relating records https://laravel.com/docs/5.6/eloquent-relationships#inserting-and-updating-related-models
before answering your question you should know that Laravel's documentation explains how to do this.
But in order to save a related Model you first need to create a fake one, or in your case relate a role you have already created. In order to do this you could first create a Role factory using something like this:
$factory->define(App\Role::class, function (Faker $faker) {
$randomRoleAlreadyCreated = \App\Role::all()->random();
return [
'name' => $randomRoleAlreadyCreated->name,
'parent_id' => $randomRoleAlreadyCreated->parent_id
];
});
As you can see on Role factory I created I pull a random Role since you stated that you already created them manually, so if you choose one randomly then your companys will be related to one of your roles randomly!
Once you have: Roles created in DB, factory of roles, you could relate random roles to a company using the factory to save a random instance.
factory(Company::class, 10)->create()->each(function ($company) {
$company->roles()->save(factory(App\Role::class)->make()); // Don't now how to do here
});
Update
If you want to save multiple roles for each company you could do this:
factory(Company::class, 10)->create()->each(function ($company) {
// Instead of 4 you could also create a random number
// using $numberOfRolesToAttach = rand($min,$max)
for($i = 1; $i <= 4; $i++) :
$company->roles()->save(factory(App\Role::class)->make());
endfor;
});

Categories