I'm trying to associate related models during database seeding in Laravel 4. According to the documentation here, I can do it like this:
$user->roles()->attach(1);
So, in my database seed I'm running:
$package = Package::create([
'name' => $faker->word,
'summary' => $faker->sentence,
'base_price' => $faker->randomFloat(2, 200, 10000)
]);
// Attach 1-5 randomly selected items to this package
foreach(range(1, 5) as $index)
{
$randomItem = Item::orderBy(DB::raw('RAND()'))->first();
$package->items()->attach($randomItem->id);
}
The packages items have already been seeded at this point, and they seed without problems. The above code gives this from Artisan though:
[BadMethodCallException]
Call to undefined method Illuminate\Database\Query\Builder::attach()
Someone here seems to think that the attach() method doesn't actually exist and the docs are wrong, but I find that hard to believe.
TL;DR What is the correct way to create many-to-many relationships in Eloquent?
The function items() in your Package model has to return a BelongsToMany relationship in order to use attach().
public function items() {
return $this->belongsToMany('Item');
}
Related
I want to include soft deleted items with HasManyThrough relationship,
I tried adding ->withTrashed(); to it, but then I got this error:
BadMethodCallException Call to undefined method
Illuminate\Database\Eloquent\Relations\HasManyThrough::withTrashed()
Is it possible to force Laravel skip that where table.deleted_at is null in a relationship?
This is an old question, but responding for anyone that has the same issue.
if your are using HasManyThrough and want to fetch also the soft deleted records. there is a scope in the HasManyThrough relationship that can include them.
You should use it like follow :
$yourModel->yourHasManyThroughRelation()->withTrashedParents()->withTrashed();
Should work the way you are trying.
I made an example to make it more clear to understand:
Group::with('users')->get();
And in the Group model:
public function users()
{
return $this->hasManyThrough(User::class, UserGroup::class, 'group_id', 'id', 'id', 'user_id')->withTrashed();
}
According to Laravel's documentation on defining relationships within model factories:
You may also attach relationships to models using Closure attributes in your factory definitions. For example, if you would like to create a new User instance when creating a Post, you may do the following:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
}
];
});
The issue I'm running into is the reference to create() within the relationship definition. It seems to me that this doesn't belong here.
It works great if I am wanting to persist my relationships to the database:
factory(App\Post::class)->create();
By running the code directly above this, a new App\Post and a new App\User will be created and both persisted to the database.
But if I just want to new up the model(s) and not persist anything (at all) to the database by running:
factory(App\Post::class)->make();
It does what I want up to a certain point. A new App\Post instance is created but not persisted, however App\Comment is created and is persisted to the database.
It seems to me, that what I really want is something like this:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
// here I only want to declare the relationship,
// not decide whether I want to create() or make()
// the relationship, something like:
return factory(App\User::class)->createOrMake()->id;
// or perhaps something like:
return factory(App\User::class)->id;
}
];
});
The end result is that I want the related data to respect what I'm trying to do from the top of the call, down. Make everything. Or create everything.
Am I doing something wrong here? Or is this something that doesn't currently exist?
Thanks!
You want the lazy() method for your related models!
At least in Laravel 5.5.
I found your question here because I was having the same issue. I found the answer thanks to Laravel's beautifully written code -- lazy() was defined just above the make() XDebug took me to -- and I've tried it out and it seems to work.
This is what I'm doing:
$factory->define(App\ArtItem::class, function (Faker $faker) {
return [
// 'id' => $faker->randomDigit,
'slug' => $faker->unique->word,
'artist_id' => factory(App\Artist::class)->lazy(),
'image_id' => factory(App\Image::class)->lazy(),
'created_at' => $faker->dateTime,
'updated_at' => $faker->dateTime,
'deleted_at' => $faker->dateTime,
];
});
Lazy() is an interesting creature that returns a closure, so you can't do factory(App\Image::class)->lazy()->id but I'm still seeing it successfully setting the correct image_id and artist_id.
I certainly hope you found the solution long before this, but maybe this'll help someone else!
This is finally solvable now using Factory Callbacks added in Laravel 5.6.12.
$factory->afterMaking(Post::class, static function (Post $post) {
if (!$post->user_id) {
$post->user()->associate(factory(User::class)->make(['id' => 0]));
}
});
$factory->afterCreating(Post::class, static function (Post $post) {
if (!$post->user_id) {
$post->user()->associate(factory(User::class)->create())->save();
}
});
Now, whenever you make a Post, if you don't pass it a user_id, it will make a User for you and set the relationship without saving it. But if you create a Post, it will persist a new User to the database and save its ID to the Post model.
Note that both the afterMaking and afterCreating closures are called when you create a factory model. I am temporarily setting the User id to 0 in case the foreign key posts.user_id was defined not null. It will be updated to the correct value in afterCreating. This is a terrible hack, but it works until we get a proper beforeCreating hook which would allow us to create the related model first.
I want a database with two tables Users and Companies and the users table has a foreign key with the company id. So 1 company can have multiple users.
I created the models for this in laravel and also created a factory for each of the tables.
In my seeders I want to create multiple data lines at once and found this solution:
factory(App\Company::class, 10)->create();
This works fine for the company table. But I need to extend this for the users table so it searches for available company Ids.
Any ideas how I can do this?
If I can't search for available Ids, I would also be happy to extend it with the "company_id" field and give it random value from 1-10 (beacause I know that these are the Ids for now).
So basically I want to extend this to use the fields from the factory, but extend it with another field "company_id":
factory(App\Users::class, 10)->create();
Here is the User factory code:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'first_name' => $faker->firstName,
'last_name' => $faker->lastName,
'postcode' => $faker->postcode,
'city' => $faker->city
];
});
When dealing with such relationships, defining model factories can be a pain. You have a few options though.
Random blind IDs
If you're sure that a common range of IDs is available, you can just generate a random ID to make the relationship work:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
// ...
'company_id' => rand(1, 10),
];
});
Inline factories
This was the most widely used approach as promoted by Laracast's screencasts:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
// ...
'company_id' => factory(App\Company::class)->create()->id,
];
});
Or you can go the other way around and create one or more users in the company factory. This way, you only need to run factory() on one of your entities.
Random ID from database
As of Laravel's 5.2 release, you have access to an Eloquent method called inRandomOrder:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
// ...
'company_id' => Company::inRandomOrder()->first()->id,
];
});
Alternatives for Laravel releases before 5.2:
// Laravel 4.2.7 - 5.1:
User::orderByRaw("RAND()")->get();
// Laravel 4.0 - 4.2.6:
User::orderBy(DB::raw('RAND()'))->get();
// Laravel 3:
User::order_by(DB::raw('RAND()'))->get();
// Source: http://stackoverflow.com/a/13931676/65732
It looks like that, you want to create Companies and Users and also you want to attach users with companies at the same time. If this is what you are trying to achieve then you can do it easily using something like this:
factory(App\Company::class, 10)->create()->each(function($factory) {
$factory->users()->save(factory(App\User::class)->make());
});
In this case, you have to define the users relationship method in your Company model using the right foreign key.
This will create 10 companies and return a collection so on that collection using each method a single user will be created and attached with that company. Also, check the documentation.
If you're using Laravel 5.2 or above you can use this:
'company_id' => Company::inRandomOrder()->first()->id
Recently I have been trying to seed my database using Laravel seeding through Model Factories and Faker.
For simple schemas, it is just a breeze to have it working :). However, I have encountered several problems when working with complex DB schemas which involve foreign keys and table relationships:
One to One
One to Many
Many to Many
...Like the one described in the link:
Laravel 5.1 foreign keys in model factory.
In this topic, the official documentation suggests to run the database seeds like this:
public function run()
{
factory(App\User::class, 50)->create()->each(function ($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
}
... but there is one problem with this solution: when working with many DB tables and running many seeds (with many relations between them), it is common to create many unnecessary models using this methodology. For instance, if we had run the PostsTableSeeder.php before the one of the above example, all those posts would not have been linked to users, and would never be used in tests and development...
So searching for a way to handle this situation, I have come up to a functional solution that works for me and avoids the unnecessary creation of those 'orphan' models...
And I wanted to share it with everyone, so it is just explained in the answer :).
So here is my solution:
The example deals with:
Users & Profiles (for illustrating One to One relationships)
Users & Posts (for illustrating One to Many relationships)
// ONE TO ONE relationship (with Users already created)
$factory->define(App\Profile::class, function (Faker\Generator $faker) {
return [
'user_id' => $faker->unique()->numberBetween(1, App\User::count()),
// Rest of attributes...
];
});
// ONE TO MANY relationship (with Users already created)
$factory->define(App\Posts::class, function (Faker\Generator $faker) {
$users = App\User::pluck('id')->toArray();
return [
'user_id' => $faker->randomElement($users),
// Rest of attributes...
];
});
Here is a solution to make relationships that is way better than assigning random Users, especially if you need to send extra information to this model.
$factory->define(App\Post::class, function (Faker\Generator $faker) {
$user = factory('App\Models\User')->create(['email' => 'email#test.com',]);
// do your relationships here (...)
return [
'user_id' => $user->id,
'title' => $faker->sentence,
'body' => $faker->paragraph,
];
}
And I saw another example with the use of anonymous function
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'user_id' => function () {
return factory(App\User::class)->create()->id;
},
'title' => $faker->sentence,
'body' => $faker->paragraph,
];
}
Source: https://laracasts.com/series/laravel-from-scratch-2017/episodes/22
This is what I use for FKs on factories
return [
'user_id' => $this->faker->randomElement(User::pluck('id')),
...
];
Note: make sure that your factories run in the right order
I have a relationship in my laravel application,
//Organsiation __has_many__ users (members)
public function users()
{
return $this->belongsToMany('User')->withPivot('is_admin');
}
public function organisations()
{
return $this->belongsToMany('Organisation')->withPivot('is_admin');
}
When I edit an organisation I try and sync the organisation_user table using,
$organisation->users()->sync($members);
The argument passed looks like this,
array(1 => array('is_admin' => 1)) as it states in the laravel documentation.
However I getting the following error returned from the server,
BadMethodCallException","message":"Call to undefined method Illuminate\\Database\\Query\\Builder::sync()
I am wanting to use sync as if my $members array contains new members, or doesnt contain an existing member, it will update the pivot table correctly, what I cannot understand is why it is not working.
I thought sync() was meant for many-to-many relationships?