Adding Relations to Laravel Factory Model - php

I'm trying to add a relation to a factory model to do some database seeding as follows - note I'm trying to add 2 posts to each user
public function run()
{
factory(App\User::class, 50)->create()->each(function($u) {
$u->posts()->save(factory(App\Post::class, 2)->make());
});
}
But its throwing the following error
Argument 1 passed to Illuminate\Database\Eloquent\Relations\HasOneOrMany::s
ave() must be an instance of Illuminate\Database\Eloquent\Model, instance
of Illuminate\Database\Eloquent\Collection given
I think its something to do with saving a collection. If re-write the code by calling each factory model for the post separately it seems to work. Obviously this isn't very elegant because if I want to persist 10 or post to each user then I'm having to decalare 10 or lines unless I use some kind of for loop.
public function run()
{
factory(App\User::class, 50)->create()->each(function($u) {
$u->posts()->save(factory(App\Post::class)->make());
$u->posts()->save(factory(App\Post::class)->make());
});
}
* UPDATED *
Is there any way to nest the model factory a 3rd level deep?
public function run()
{
factory(App\User::class, 50)
->create()
->each(function($u) {
$u->posts()->saveMany(factory(App\Post::class, 2)
->make()
->each(function($p){
$p->comments()->save(factory(App\Comment::class)->make());
}));
});
}

Since Laravel 5.6 there is a callback functions afterCreating & afterMaking allowing you to add relations directly after creation/make:
$factory->afterCreating(App\User::class, function ($user, $faker) {
$user->saveMany(factory(App\Post::class, 10)->make());
});
$factory->afterMaking(App\Post::class, function ($post, $faker) {
$post->save(factory(App\Comment::class)->make());
});
Now
factory(App\User::class, 50)->create()
will give you 50 users with each having 10 posts and each post has one comment.

Try this. It worked for me:
factory(\App\Models\Auth\User::class, 300)->create()->each(function ($s) {
$s->spots()->saveMany(factory(\App\Models\Spots\Parking::class, 2)->create()->each(function ($u) {
$u->pricing()->save(factory(\App\Models\Payment\Pricing::class)->make());
}));
$s->info()->save(factory(\App\Models\User\Data::class)->make());
});

For a 3rd level nested relationship, if you want to create the data with the proper corresponding foreign keys, you can loop through all the results from creating the posts, like so:
factory(App\User::class, 50)->create()->each(function($u) {
$u->posts()
->saveMany( factory(App\Post::class, 2)->make() )
->each(function($p){
$p->comments()->save(factory(App\Comment::class)->make());
});
});

To answer the initial problem / error message:
The problem indeed has to do with saving the data. Your code:
$u->posts()->save(factory(App\Post::class, 2)->make());
... should be changed to
$u->posts()->saveMany(factory(App\Post::class, 2)->make());
From the laravel docs:
You may use the createMany method to create multiple related models:
$user->posts()->createMany(
factory(App\Post::class, 3)->make()->toArray()
);
That means:
when only creating one model with the factory, you should use save() or create()
when creating multiple models with the factory, you should use saveMany() or createMany()

$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', //
password
'remember_token' => Str::random(10),
];
});
$factory->define(Post::class, function ($faker) use ($factory) {
return [
'title' => $faker->sentence(3),
'content' => $faker->paragraph(5),
'user_id' => User::pluck('id')[$faker->numberBetween(1,User::count()-1)]
];
});

Let me, explain How to add multi level of relationship of call factory in Laravel 9 By concept of new school and old school.
The new school is:
\App\Models\Author::factory()
->has(
\App\Models\Article::factory(1)
->has(
\App\Models\Comment::factory(9)
->has(
\App\Models\Reply::factory(2)
)
))->create();`enter code here`
That's for Laravel 9 . There's anthor way call Magic method. let me explain that:
\App\Models\Author::factory()->hasArticles(1)->hasComments(9)->hasReplies(2)->create();
this hasArticles() is the name of method of relationship in parent model should convert the name with has. for example: comments() convert to hasComments().
Now lets explain old school that's still prefect in some cases and still works good with Laravel 9.
\App\Models\Author::factory(1)->create()
->each(function($a) {
$a->articles()->saveMany( \App\Models\Article::factory(2)->create() )
->each(function($p){
$p->comments()->saveMany(\App\Models\Comment::factory(5))->create()
->each(function($r){
$r->replies()->saveMany(\App\Models\Reply::factory(5))->create();
});
});
});
of course you can replace method saveMany() by save() as your relationship you have.
also you can replace method create() by make() if you want to doesn't save in database for test purposes.
Enjoy.

In version laravel 9, use like this
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* #extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array<string, mixed>
*/
public function definition()
{
return [
'user_id' => User::factory(),
'title' => fake()->paragraph()
];
}
}```

Related

Laravel Query Builder hard coded data is not inserted with post method

I am trying to insert hard coded data with QueryBuilder insertGetId() method.
my code is-
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class StudentController extends Controller
{
public function addStudent()
{
$foreign_key = DB::table('students')->insertGetId([
'id' => 'stu-000002',
'name' => 'Ahsan',
'email' => 'ahsan#example.net',
]);
echo $foreign_key;
}
}
My migration file is-
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('students', function (Blueprint $table) {
$table->string('id', 30)->primary();
$table->string('name', 100);
$table->string('email', 100)->unique();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('students');
}
};
My route is -
Route::post('/add-student', [StudentController::class, 'addStudent']);
But result is -
Symfony \ Component \ HttpKernel \ Exception \ MethodNotAllowedHttpException
The GET method is not supported for this route. Supported methods: POST.
But when I try to insert data with get method like-
Route::get('/add-student', [StudentController::class, 'addStudent']);
Data has been inserted . But primary key has been retrived 0 as primary key is custom string.
How can I solve this problem. Thank you.
run this command in terminal:
php artisan route:cache
So what this command does is, it registers new routes or gives an error if there is something wrong with your route file.
There are two problems with what you're doing:
Problem 1:
The first is the MethodNotAllowedException. I guess you're trying to use a GET request on a POST URL. This won't work, because Laravel blocks the 'wrong' method.
Use POST when you have data to submit (or if you really want to stick to the 'use post when saving'-creed use a form). Use GET when you want to access an URL.
Problem 2
According to the API (This one) insertGetId returns an integer which is the ID. Since your ID's are strings, you can't use that method.
A solution to that problem would be to change the code like this:
public function addStudent()
{
$student_id = 'stu-000002'
$insert = DB::table('students')->insert([
'id' => $student_id,
'name' => 'Ahsan',
'email' => 'ahsan#example.net',
]);
if ( ! $insert ) {
return false;
}
echo $student_id;
}
The insert method returns a boolean. Leveraging that you can check whether or not the entry was inserted. If it is, your ID should be good.

Laravel - hasOne relation based on model attribute

I have a problem, I need to make a relation based on an attribute from the model$this->type, my code is as follows:
class Parent extends Model {
protected $attributes = [
'type' => self::TYPE_A
];
public function child()
{
return match ($this->type) {
self::TYPE_A => $this->hasOne(Cousin::class)->where('type', Cousin::TYPE_A),
self::TYPE_B => $this->hasOne(Cousin::class)->where('type', Cousin::TYPE_B),
self::TYPE_C,
self::TYPE_D => $this->hasOne(Cousin::class)->where('type', Cousin::TYPE_C),
self::TYPE_E,
self::TYPE_F => $this->hasOne(Cousin::class)->where('type', Cousin::TYPE_D)
};
}
public function children()
{
return $this->hasMany(Cousin::class);
}
}
If I do this inside artisan tinker, I get the child relation correctly:
$parent = Parent::first();
$parent->child;
However if my queues execute the code the child relation is returned as null.
If I put logger($this) in the first line of the relation I get an empty model when inside queues:
[2021-09-29 23:51:59] local.DEBUG: {"type":1}
Any idea on how to solve this?
Apparently it is not possible to use the relationship in this way.
The relationship I want uses composite keys and Eloquent doesn't support it.
They created a package to enable this to happen: https://github.com/topclaudy/compoships
But I don't want to add any more packages to my application as it is already too big.
My solution was to add one more column in the cousins table to know which one to use:
public function child() {
return $this->hasOne(Cousin::class)
->where('main', true);
}
protected $attributes = [
'type'
];
public function getTypeAttribute() {
return $this->hasOne(Cousin::class)
->where('main', true);
}
Then call it anywhere
$parent->type
Or you can do it as follow
public function typeMainTrue() {
return $this->hasOne(Cousin::class)
->where('main', true);
}
Then call it anywhere
$parent->typeMainTrue

Problem with Laravel 8: Call to a member function count() on null

I'm a newcomer to Laravel, and I got errors when I tried to generate some info in the table in the database using a factory.
Call to a member function count() on null " at
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/HasFactory.php:17.
Maybe somebody had the same problem? I will be grateful if someone can help. Below will be the code of certain elements:
Seeder
class UsersTableSeeder extends Seeder
{
public function run()
{
Users::factory()->count(30)->create();
}
}
Factory
class UploadInfoFactory extends Factory
{
protected $model = Users::class;
public function definition()
{
return [
'Name' => $this->faker->name,
'Birthday' => $this->faker->date('d-m-Y'),
'Phone number' => $this->faker->phoneNumber,
'Phone balance' => $this->faker->numberBetween(-50,150),
];
}
}
DatabaseSeeder
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call(UsersTableSeeder::class);
}
}
Migration
class CreateInfoUsers extends Migration
{
public function up()
{
Schema::create('info_users', function (Blueprint $table) {
$table->integerIncrements('id');
$table->string('name',100);
$table->date('Birthday');
$table->string('Phone number',100);
$table->string('Phone balance',100);
});
}
}
The error code that pops up in bash when we enter php artisan db: seed:
Call to a member function count() on null at vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/HasFactory.php:17
13▕ public static function factory(...$parameters): Factory {
14▕ $factory = static::newFactory() ?: Factory::factoryForModel(get_called_class());
15▕
16▕ return $factory
➜ 17▕ ->count(is_numeric($parameters[0] ?? null) ? $parameters[0] : null)
18▕ ->state(is_array($parameters[0] ?? null) ? $parameters[0] : ($parameters[1] ?? []));
19▕ }
20▕
21▕ /**
This might help someone else cause my problem was different. When seeding the database, Laravel printed out the same error that #Yurii experienced.
Seeding: Database\Seeders\ProfileSeeder
Error
Call to a member function count() on null
at vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/HasFactory.php:18
Indeed my artisan console failed to create the Factory for my Profile model
$ php artisan tinker
>>> use Illuminate\Database\Eloquent\Factories\Factory;
>>> Factory::factoryForModel(Profile::class);
=> null
After some minutes of investigation, I found out that I forgot to return the Factory in the configure method
class ProfileFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Profile::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
// some stuff
];
}
public function configure()
{
// it miss the return here !!
$this->afterCreating(function (Profile $profile) {
// do stuff with $profile
});
}
}
So, when the Factory was instantiated, the configure method was called but no Factory was returned! Placing the return fixed the issue.
If you want a model to automatically use a factory you would have to name the factory in a particular way, otherwise you would have to define a way to resolve the particular factory you want to use for the model.
Rename your factory to UsersFactory and it will automatically be used be the Users model.
Though, I suggest renaming Users to User as the convention is for models to be in the singular and tables to be in the plural. If you change the model name you would then need to change the factory to UserFactory to match.
"The HasFactory trait's factory method will use conventions to determine the proper factory for the model. Specifically, the method will look for a factory in the Database\Factories namespace that has a class name matching the model name and is suffixed with Factory. If these conventions do not apply to your particular application or factory, you may overwrite the newFactory method on your model to return an instance of the model's corresponding factory directly"
Laravel 8.x Docs - Database Testing - Creating Models Using Factories - Connecting Factories and Models

Laravel not booting trait/authing user in unit tests

I can't figure out if Laravel is failing to boot my model trait, or is simply not seeing the user as being authed within the trait.
All of this code works perfectly fine when I test my app manually, but when I run unit tests I am getting a bunch of errors.
Here is the trait I've added to App\User and a few other models:
trait HasCompany
{
public static function bootHasCompany()
{
if (auth()->check()) {
static::creating(function ($model) {
$model->company_id = auth()->user()->company_id;
});
static::addGlobalScope('company_id', function (Builder $builder) {
$builder->where('company_id', auth()->user()->company_id);
});
}
}
public function company()
{
return $this->belongsTo('App\Company');
}
}
The purpose of this trait is to automatically add the logged in users company_id to any models they create, and restrict their access only to models they have created. I should mention that all App\User's have a company_id set in the database.
So as I've said, when I attempt to create a model when logged into my app everything works great. The trait works perfectly. However, unit tests don't seem to care for this trait. Here is an example test that does not work:
class RoleTest extends TestCase
{
use WithFaker;
public $user;
public function setup()
{
parent::setUp();
$this->user = App\User::create([
'company_id' => $faker->randomNumber(),
'name' => $this->faker->firstName,
'email' => $this->faker->email,
'password' => $this->faker->password,
]);
}
public function tearDown()
{
parent::tearDown();
$this->user->delete();
}
public function testAdd()
{
$response = $this->actingAs($this->user)->json('POST', 'roles/add', [
'_token' => csrf_token(),
'name' => $this->faker->word,
]);
$response->assertStatus(200)->assertJson(['flash' => true]);
}
}
I'm getting a 500 response instead of a 200 response because the model should automatically be obtaining the company_id from $this->user, but it is not. This is only happening for unit tests.
Here is the model code:
class Role extends Model
{
use HasCompany;
protected $fillable = ['company_id', 'name'];
}
Why aren't the unit tests booting the trait properly? It seems like actingAs doesn't work for authorization within traits, or is failing to boot it's traits entirely.
In your unit tests, the user model is first booted when the user is created by the setup function. At that time, no user is authenticated (as actingAs follows later). So, the auth()->check() only happens once when the user is created.
I think instead of checking if authenticated once (during boot), you should check if authenticated during the user creation.
Remove if(auth()->check()) from bootHasCompany and add it inside the Eloquent event closures like so:
static::creating(function ($model) {
if(auth()->check())
{
$model->company_id = auth()->user()->company_id;
}
});
static::addGlobalScope('company_id', function (Builder $builder) {
if(auth()->check())
{
$builder->where('company_id', auth()->user()->company_id);
}
});

Laravel VentureCraft revisionable not working on Laravel 5.5 when updating model

I used this package called revisionable so I tried to add it to my package.json and run the migration and everything worked fine.
But when I try to create a record and then update them it doesn't fill out the revisions table?
I am using "venturecraft/revisionable": "^1.28", and Laravel 5.5
Here is my code in my model
So here is what I have done in my model
use Venturecraft\Revisionable\Revisionable;
use Venturecraft\Revisionable\RevisionableTrait;
class AnalysisRequest extends Revisionable
{
use SoftDeletes;
use RevisionableTrait;
protected $revisionEnabled = true;
protected $revisionCleanup = true;
protected $historyLimit = 100; //Stop tracking revisions after 500 changes have been made.
protected $dontKeepRevisionOf = array(
'client_id', 'service_id', 'categories_id', 'methodologies_id', 'current_version', 'due_date'
);
protected $keepRevisionOf = array(
'sample_description',
'special_instruction',
'status',
'rushable'
);
When did I did wrong?
Can someone shed some light for me.
Thanks in advance.
For those who uses this package out there. There is no problem with the new versions of Laravel.
The problem is by using the eloquent methods of laravel especially with the update method.
So instead of updating a model like this example below.
$analysis_request = AnalysisRequest::where('id', $input['id'])->update([
'client_id' => $input['client_id'],
'sample_description' => $input['sample_description'],
'special_instruction' => $input['special_instruction'],
'rushable' => $input['rushable'],
'status' => 'for_testing'
]);
You have to do it this way in order to make revisions of the model. See below
$analysis_request = AnalysisRequest::where('id', $input['id'])->first();
$analysis_request->client_id = $input['client_id'];
$analysis_request->sample_description = $input['sample_description'];
$analysis_request->special_instruction = $input['special_instruction'];
$analysis_request->status = 'for_testing';
$analysis_request->save();
As you can see I used the first() method to fetch my model and update it using save() You can't use the save() method if you don't use the first().
Reference link of the issue in github.
I know it's hard to do it like that way. But for now you will be forced to if you want to make your life easier when creating revisions instead of manually doing it on your own. But hey it still depends on your case.
I hope the author makes a fix on this soon in the next release.
Okay, I did some digging on this, and it looks like it's a limitation of the package (it doesn't include the updating event - ref: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php).
You can try to "override" the trait function and add the event yourself though:
class AnalysisRequest extends Model {
use RevisionableTrait {
bootRevisionableTrait as protected unused;
}
....
//Override trait function
public static function bootRevisionableTrait() {
static::saving(function ($model) {
$model->preSave();
});
static::saved(function ($model) {
$model->postSave();
});
static::created(function($model){
$model->postCreate();
});
static::deleted(function ($model) {
$model->preSave();
$model->postDelete();
});
//Add in the update events
static::updating(function ($model) {
$model->preSave();
});
static::updated(function ($model) {
$model->postSave();
});
}
}

Categories