Factory & Seeder for Pivot Table - laravel

I am trying to create a factory and seeder for a pivot table. I am trying to do so without creating a model just for the pivot table. However, in doing so, I am getting an error. Before I show the error, here are my files:
// CategoryNewsFactory.php
use App\Model;
use Faker\Generator as Faker;
$factory->define(Model::class, function (Faker $faker) {
DB::table('category_news')->insert(
[
'category_id' => factory(App\Category::class)->create()->id,
'news_id' => factory(App\News::class)->create()->id,
]
);
});
// CategoriesNewsSeeder.php
public function run()
{
factory(App\Model::class, 35)->create();
}
And here is my error message:
Error
Class 'App\Model' not found
at C:\laragon\www\startup-reporter\vendor\laravel\framework\src\Illuminate\Database\Eloquent\FactoryBuilder.php:232
228| if ($this->amount < 1) {
229| return (new $this->class)->newCollection();
230| }
231|
> 232| $instances = (new $this->class)->newCollection(array_map(function () use ($attributes) {
233| return $this->makeInstance($attributes);
234| }, range(1, $this->amount)));
235|
236| $this->callAfterMaking($instances);
1 C:\laragon\www\startup-reporter\vendor\laravel\framework\src\Illuminate\Database\Eloquent\FactoryBuilder.php:169
Illuminate\Database\Eloquent\FactoryBuilder::make([])
2 C:\laragon\www\startup-reporter\database\seeds\CategoriesNewsTableSeeder.php:14
Illuminate\Database\Eloquent\FactoryBuilder::create()
So I am wondering, what am I doing wrong and how can I fix it?
Thanks.

Firstly, you can create the model for your pivot table as far as Model class (in your case) doesn't exist at all. Secondly, you can attach/sync one model to another instead of creating a pivot record manually. Finally, you can use the following code inside factory(App\Category::class)
$factory->afterCreating(Category::class, function (Category $category, $faker) {
$category->news()->save(factory(News::class)->make());
});

Related

How to add rows with unique column to the database?

I have a table with unique column, it consists of 8 characters of random English letters and numbers.
I need to be able to seed the table by adding more data to the existing data.
I am supposed to enter a given amount of rows.
I tried using Factories and Seeders but then realized it's not working as expected:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Code extends Model
{
protected $table = 'codes';
}
protected $fillable = [
'code',
'is_valid'
];
Factory:
public function definition()
{
return [
'code' => $this->faker->regexify('[A-Z0-9]{8}')
];
}
Then I am supposed to add data by running:
$codes = Code::factory()->count(10000)->create();
and after that, if I run it multiple times I might end up with constraint violation:
// add some more data
$codes = Code::factory()->count(10000)->create();
$codes = Code::factory()->count(10000)->create();
What is an efficient way to be able to add given amount of rows and make sure they're unique?
All other questions I've seen here are similar, but not working for my case.
Change in your factory definition this line from this: 'code' => $this->faker->regexify('[A-Z0-9]{8}') to: $faker->unique()->regexify('[A-Z0-9]{8}');.
In your factory you can use the unique() faker modifier : https://fakerphp.github.io/#modifiers
public function definition()
{
return [
'code' => $this->faker->unique()->regexify('[A-Z0-9]{8}')
];
}
Edit: when running the factory with existing data, you can check if the faked data already exists in the database like:
public function definition()
{
do {
$fakeCode = $this->faker->unique()->regexify('[A-Z0-9]{8}');
} while (DB::table('codes')->where('code', $fakeCode)->exists());
return [
'code' => $fakeCode
];
}

Where clause in polymorphic relationship

I have the tables
threads
- id
replies
- id
- repliable_id
- repliable_type
I want to add another column to the Thread, which is the id of the most recent reply.
Thread::where('id',1)->withRecentReply()->get()
And the following query scope
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->whereHasMorph('repliable', ['App\Thread'], function ($q) {
$q->where('repliable_id', '=', 'threads.id');
})->latest('created_at')
->take(1),
]);
}
I have also tried
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->where('repliable_id', '=', 'threads.id')
->latest('created_at')
->take(1),
]);
}
But in both cases the
recent_reply_id => null
If instead of threads.id i enter an integer, it works and the recent_reply_id is not null
For example
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->whereHasMorph('repliable', ['App\Thread'], function ($q) {
$q->where('repliable_id', '=', 1);
})->latest('created_at')
->take(1),
]);
}
My question is
Is there a way to be able to fetch the recent_reply_id using the respective threads.id ?
I would suggest using appends instead of query scopes so in your Thread model we will add
protected $appends = ['recent_reply_id'];
.....
public function replies () {
// you need to add here your relation with replies table
}
public function getRecentReplyIdAttribute () {
return $this->replies()->latest('id')->first()->id;
}
now wherever you query the threads table you will have access to recent_reply_id like
$thread = Thread::where('id',1)->withRecentReply()->get();
$thread->recent_reply_id;

Laravel 5.8 factory states Unable to locate factory with name [default] [CLASS]

I'm trying to create two different sets of seeders and attempting to use Laravel's factory states to specify different factories for each seeder. Here is an basic example of a BoopieFactory:
$factory
->state(App\Models\Boopie::class, 'one_flavored', function (Faker $faker) {
return [];
});
$factory
->state(App\Models\Boopie::class, 'two_flavored', function (Faker $faker) {
return ['something different'];
});
I was then under the assumption that I could call either factory(\App\Models\Boopie::class, 25)->states('one_flavored')->create(); or factory(\App\Models\Boopie::class, 25)->states('two_flavored')->create();. These result in the above mentioned Unable to locate factory with
name [default] [App\Models\Boopie]. error. Am I missing something small here, or is this just a complete mis-understanding of factory states? Or do I also still need to define the factory first and then chain state on? Laravel's docs are usual great, but lack in this department. I was also looking at defineAs (undocumented, but in the eloquent source code) to attempt to create two different aliases for the same class with different returns. Something like:
$factory
->defineAs(App\Models\Boopie::class, 'OneFlavor', function (Faker $faker) {
return [];
});
$factory
->defineAs(App\Models\Boopie::class, 'TwoFlavor', function (Faker $faker) {
return ['Something different'];
});
And then I'm trying to call like so: factory('OneFlavor', 25)->create(); and getting the exception: Class 'OneFlavor' not found. Perhaps there's another way to go about this altogether, or I'm just missing something? I have also tried to nest it like so, which seems to be closer but still has the exception: Unable to locate [oneFlavor] state for [App\Models\Boopie].
$factory->define(App\Models\Boopie::class, function (Faker $faker) use ($factory) {
$factory->state(App\Models\Boopie::class, 'oneFlavor', function ($faker) {
return [];
});
$factory->state(App\Models\Boopie::class, 'twoFlavor', function ($faker) {
return ['Someting different'];
});
return [];
});
Thanks in advance 😀!
Ok, solved like so:
$factory->define(App\Models\Boopie::class, function (Faker $faker) {
return [
'thingOne' => 'red shirt',
'thingTwo' => 'blue shirt'
];
});
$factory->state(App\Models\Boopie::class, 'usiFlavored', function (Faker $faker) {
return [
'thingOne' => 'blue shirt',
'thingTwo' => 'red shirt'
];
});
You need to first define a default factory with attributes and then your various states can specify different values for those attributes.

Laravel 5.6 - Using model functions in ModelFactory

I am working with Laravel 5.6 and found myself a weird problem while extending the functionality of my project.
Right now i need to create two new models: order and item. It was quite easy to fill the items table with dummy data using Faker and Laravel Factories/Seeders. The biggest problem is while working with the order model.
This little fellow is related to a company with a foreign key named company_id and user with a foreign key named seller_id. The company field is okay, the trouble is behind my seller_id
This seller needs a role related to the company my factory will randomly pick for it because the user is not related to the company (directly) and i can't just look for it with a company_id.
In order to get all the users "related" to my company, i've created the next function on my Company model:
public function users()
{
$roles = $this->roles;
$users = [];
foreach ($roles as $role) {
foreach ($role->users as $user) {
$user->makeHidden(['pivot']);
array_push($users, $user);
}
}
$users = array_unique_objects($users);
return $users;
}
btw: I'm using laravel-permissions, a library made by Spatie.
What this functions does is get every role from a company and then it pushes it to an array of users.
This custom helper: array_unique_objects tracks any repeated user on my array and removes them.
That function works find because i've tested on a couple of controllers so i know there is no problem with it. Either way, my OrderFactory.php looks like this:
<?php
use Faker\Generator as Faker;
use App\Models\User;
use App\Models\Company;
$factory->define(App\Models\Order::class, function (Faker $faker) {
$company = Company::get()->random(1);
$users = $company->users();
$user = array_random($users);
return [
'company_id' => $company,
'seller_id' => $user->id,
'code' => strtoupper(str_random(10)),
'description' => $faker->sentence($nbWords = rand(2, 4), $variableNbWords = true),
'created_at' => $faker->dateTimeBetween($startDate = '-1 year', $endDate = 'now', $timezone = null)
];
});
But when i run the php artisan db:seed command, it throws the next error in console:
BadMethodCallException : Method Illuminate\Database\Eloquent\Collection::users does not exist.
at >/home/ironman/Documentos/Sandbox/Proventas/Backend/vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php:99
95| */
96| public function __call($method, $parameters)
97| {
98| if (! static::hasMacro($method)) {
99| throw new BadMethodCallException(sprintf(
100| 'Method %s::%s does not exist.', static::class, $method
101| ));
102| }
103|
Exception trace:
1 Illuminate\Support\Collection::__call("users", [])
/home/ironman/Documentos/Sandbox/Proventas/Backend/database/factories/OrderFactory.php:10
2 Illuminate\Database\Eloquent\Factory::{closure}(Object(Faker\Generator), [])
/home/ironman/Documentos/Sandbox/Proventas/Backend/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:274
Please use the argument -v to see more details.
Is there anything I can do to fix this problem? I know that using Laravel Relationships will fix my problem but the specifications of this project says that i have to keep things just as the are.
Your call to
$company = Company::get()->random(1);
does not return a single company. It returns a Collection, which does not have a users dynamic function. Try
$company = Company::get()->random(1)->first();

Laravel nested eager loading can't work as expected

This problem has troubled me for days, I cannot solve this problem until now, so I have to ask for help.
Below is the relevant code snippet:
Model Category.php
public function child()
{
return $this->hasMany('App\Http\Models\Category', 'pid', 'id');
}
public function logs()
{
return $this->hasMany('App\Http\Models\Log', 'cate_id');
}
public function products()
{
return $this->hasMany('App\Http\Models\Product', 'cate_id');
}
public function newProduct()
{
return $this->products()->orderBy('created_at', 'desc');
}
public function latestLog()
{
return $this->logs()->where([
'product_id' => $this->newProduct()->first()->id,
'status' => 1,
])->orderBy('created_at', 'desc');
}
Controller CategoryController.php
public function getLatestLog()
{
// Category with id 1 with nested eager loading
$subCategory = Category::find(1)
->with(['child.newProduct', 'child.latestLog'])
->first()->child;
// Get latest log for subCategory with id 3
dd($subCategory->find(3)->latestLog);
}
In this case, I want to use nested eager loading to get latest log. But what bothers me is when I add child.checkLatestLog it just outputs empty, but when I delete it, it will output normally.
I think the problem is related to the $this->newProduct()->first()->id variable. Because I tried to manually enter a product ID that exists in the log table, it's worked normal.
It may be my fault, but I don't know where it was wrong. I would like to thank you for asking for help.
update
A solution for this Question:
public function latestLog()
{
return $this->hasManyThrough(
'App\Http\Models\Log',
'App\Http\Models\Product',
'cate_id',
'product_id',
'id',
'id'
)->orderBy('created_at', 'desc');
}
The problem is $this->newProduct()->first()->id: You can't use eager loading with model-dependent attributes like that.
You'll have to use a JOIN solution.

Resources