Laravel eloquent multiple belongsTo seeding - php

I have three models, User, Category and Article.
Article belongs to both a User and a Category where the foreign keys user_id and category_id are not null.
Am lost trying to seed the database with fake data via factories accordingly, here's the seeding code...
// 2 categories and 12 articles, 6 per category
// TODO: A user should have 6 articles with mixed categories
// 3 from each category
// So we will have 2 users
factory(Category::class, 2)->create()->each(function($category) {
factory(User::class)->create()->each(function($user) use ($category) {
// How to assign an Article to $category or $user while creating?
$user->articles()->createMany(
factory(Article::class, 3)->make()->toArray()
); // category_id can't be null
// OR
$category->articles()->createMany(
factory(Article::class, 3)->make()->toArray()
); // user_id can't be null
});
});
I'll get one of two errors, either
Column user_id doesn't have a default value
Or
Column category_id doesn't have a default value
Which is logical due to the Article table migration
$table->increments('id');
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedInteger('category_id');
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
How can I pass the category_id or user_id to the factory call?
Is there a better way to acheive this?
Thanks in advance

You can override attributes for your factory by passing an array with the desired attributes to the make() method:
Change
$user->articles()->createMany(
factory(Article::class, 3)->make()->toArray()
);
to
$user->articles()->createMany(
factory(Article::class, 3)->make([
'category_id' => $category->id
])->toArray()
);
to override the category_id attribute.
https://laravel.com/docs/6.x/database-testing#using-factories

Seed the categories separately.
Then seed the users and inside it seed the articles.
In the article factory file ArticleFactory:
$factory->define(App\Article::class, function (Faker $faker) {
return [
'category_id' => App\Category::inRandomOrder()->value('id'),
....
'created_at' => time(),
'updated_at' => time(),
];
});

This may help you
First
make users create when you make a new article from factory
$factory->define(App\Article::class, function ($faker) use ($factory) {
return [
'user_id' => $factory->create(App\User::class)->id,
'title' => $faker->sentence,
'body' => $faker->paragraph
];
});
Second
from your seeder
in this example i will make 3 categories * 6 Articles
factory('App\Category', 3)->create()->each(function($u) {
$u->posts()->save(factory('App\Article', 6)->create());
});

Related

Problem with Polymorphic Relationships in Laravel

I'm trying to implement a way to get the details of a person depending on the group it belongs to.
My database looks like this:
persons:
id
group
type
1
person
9
2
company
30
3
person
9
and so on.
Each "group" has a model which contains detail information for this record specific to the group.
For example:
persondetails looks like this
id
person_id
firstname
lastname
birthname
1
1
Harry
Example
Bornas
2
3
Henrietta
Example
Bornas
I created models for each table and I'm no trying to implement a relationship which allows me to query a person->with('details') via the person model (for example: for a complete list of all persons no matter which type it is).
For single records I got it working via a simple "if $this->group === person {$this->hasOne()}" relation, which doesn't work for listings.
I tried to wrap my head around a way to use a polymorphic relationship, so I put the following into the person model:
public function details(){
Relation::morphMap([
'person' => 'App\Models\Persondetail',
'company' => 'App\Models\Companydetail',
]);
return $this->morphTo();
}
and a subsequent
public function person(){
return $this->morphMany(Person::class, 'details');
}
which doesn't work sadly. Where is my thinking error?
As you're not using laravel convention for the keys, you need to define the keys on your relation
public function details()
{
Relation::morphMap([
'person' => 'App\Models\Persondetail',
'company' => 'App\Models\Companydetail',
]);
return $this->morphTo(__FUNCTION__, 'group', 'type');
}
Docs Link:
https://laravel.com/docs/9.x/eloquent-relationships#morph-one-to-one-key-conventions
Based on the reply by https://stackoverflow.com/users/8158202/akhzar-javed I figure it out, but I had to change the code a bit:
Instead of the code in the answer, I had to use the following:
public function details()
{
Relation::morphMap([
'person' => 'App\Models\Persondetails',
'company' => 'App\Models\Companydetail',
]);
return $this->morphTo(__FUNCTION__, 'group', 'id', 'person_id');
}

Laravel relationship storing to pivot null

I have 3 tables called games, products, game_product. And game_product is my pivot table
This is the structure.
id
game_id
product_id
1
1
1
1
1
2
30 Minutes ago I can attach the game_id and product_id correctly, then i changed nothing. And after I tried to create a new data, its give me this error message
Call to a member function games() on null
This is my model relationship
App\Models\Game.php :
public function products()
{
return $this->belongsToMany('App\Models\Product', 'game_product', 'product_id', 'game_id');
}
App\Models\Product.php :
public function games()
{
return $this->belongsToMany('App\Models\Game', 'game_product', 'game_id', 'product_id' );
And this is my create controller
public function productsNew(Request $request, $id)
{
$products = Product::find($id);
$new = Product::create([
'product_sku' => $request->product_sku,
'name' => $request->name,
'seller_price' => $request->seller_price,
'price' => $request->price,
'profit' => $request->price - $request->seller_price,
]);
$products->games()->attach($id);
$new->save();
notify("Product added successfully!", "", "success");
return redirect('admin/products/'.$id);
}
}
I try to post the id of game and product to pivot table game_id and product_id. What should I do to store the ID only without any other of value?
Just change the order of
$products->games()->attach($id);
$new->save();
to be
$new->save();
$products->games()->attach($id);
As a side note, you are creating a product. Just 1 product. So the variable name mustn't be pluralized as it is singular. $product
One final thing, if this function is just part of the CRUD, please follow the convention of naming functions to be: create/store/show/edit/update/destroy, makes your and everyone else's lives easier when asking questions.

custom path in laravel model

I have 2 model in laravel 5.5
Article.php class with 2 function:
public function sluggable()
{
return [
'slug' => [
'source' => 'title'
]
];
}
public function path()
{
return "/$this->slug";
}
Category.php
public function childs() {
return $this->hasMany('App\Category','parent_id','id') ;
}
categories table:
id / article_id / parent_id / name
Now for example,for this code in view: {{ $article->path() }}
it prints: `example.com/article_slug`
But I want simething like this:
example.com/parentCategory/subCategory-1/.../subCategory-n/article_slug
How can I do it in path() function? Is it possible?
I'm assuming your question is asking how you can generate uri's for categories that can have an unlimited amount of children via their slugs.
How I would go about tackling something like this is by using a hierarchical data pattern within MySQL which will allow you to get a list of descendants / ancestors by performing one query. There are numerous ways to implement this, but for the purpose of this explanation I'm going to explain how to do it using the nested set pattern. More specifically, I'll be giving demonstrating how to do this using lazychaser's nested set package.
Categories Table Migration
use Kalnoy\Nestedset\NestedSet;
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
NestedSet::columns($table);
$table->timestamps();
});
The nested set columns will add _lft, _rgt, and parent_id columns to your table. I would recommend you do some research on how the nested set model works in order to understand what the left and right columns are used for.
Category Model
use Kalnoy\Nestedset\NodeTrait;
class Category extends Model
{
use NodeTrait;
//
}
Now you can create a child category like so:
$parentCategory = Category::first();
$parentCategory->children()->create([
'name' => 'Example Category'
]);
This means that on a deeply nested category you can do:
$categories = Category::ancestorsAndSelf($article->category_id);
This will return all ancestors of the above category, then to get the uri you can do something like:
$uri = $categories->pluck('slug')->implode('/') . '/' . $article->slug;
You need to use the SluggableTrait at the top of your model like so :
use Cviebrock\EloquentSluggable\Sluggable;
class Post extends Model
{
use Sluggable;
/**
* Return the sluggable configuration array for this model.
*
* #return array
*/
public function sluggable()
{
return [
'slug' => [
'source' => 'title'
]
];
}
}

Seeding a table with a primary key that is also a foreign key in Laravel

I have a Users table that acts as an abstract table for Engineers and Architects tables.
Users [id (PK), first_name, last_name, role] where 'role' is either 'Engineer' or 'Architect'
Engineers [id (PK/FK), experience, skill_set] where foreign 'id' references 'id' on 'Users'
Architects [id (PK/FK), certification, level] where foreign 'id' references 'id' on 'Users'
(The table attributes are arbitrary. I just wanted to get the idea across. Basically there's two tables that share common attributes. The common attributes have been moved in to it's own table).
After I seed the Users table I need to seed the Engineers table with unique Id's matching Users table (since it's a PK and an FK) where Users.role == 'Engineer'.
The same must be done for Architects.
My current solution is to create Users, do a query to get all users where 'role' is 'Engineer' and in a foreach statement create Engineers for each user.
class DatabaseSeeder extends Seeder
{
public function run()
{
$faker = Faker\Factory::create();
$users = factory(App\User::class, 10)->create(); // Create Users
$engineers = App\User::all()->where('role', 'engineer');
foreach($engineers as $engineer){
App\Engineer::create([
'id' => $engineer->id,
...
]);
}
}
}
How can move App\Engineer to it's own Seeder?
My thought process was to create an array of users where 'role' == 'engineer'. Then pop off a user and use that user's User->id when creating an Engineer.
class EngineersTableSeederextends Seeder
{
public function run()
{
$engineers = App\User::all()->where('role', 'engineer');
$engineer = array_pop( $engineers );
factory(App\Engineer::class, 10)->create()->each(function($e) {
//$p-> ; ???
});
}
}
I also tried this inside EngineerFactory;
use Faker\Generator as Faker;
$factory->define(App\Engineer::class, function (Faker $faker) {
return [
'id' => $faker->unique()->randomElement(App\User::all()->where('roles', 'engineer')->pluck('id')->toArray()),
...
];
});
However, because of the unique() modifier I get a Maximum retries of 10000 reached without finding a unique value error. It works if I have about a 100 Users but only create 10 Engineers.
I'm not sure if this is the right approach.
id should never be a foreign key, if your engineers needs to be related to a user, create a user_id field on engineers table. Then your seeders will be:
$users = factory(App\User::class, 10)->create(); // Create Users
$usersEngineers = App\User::all()->where('role', 'engineer');
foreach($usersEngineers as $userEngineer){
App\Engineer::create([
'user_id' => $userEngineer->id,
...
]);
}
Or you can define your engineer factory like this:
use Faker\Generator as Faker;
$factory->define(App\Engineer::class, function (Faker $faker) {
$userEngineer = factory(App\User::class)->create();
// some logic to assign engineer role to $userEngineer
return [
'user_id' => $userEngineer
...
];
});

how to ignore soft delete in laravel multiple unique columns in laravel 5.3

I have been doing a project right now. In this I am stuck. All I want that to ignore soft delete of laravel in my validation that has unique column of name, parent_id.
For example :
Suppose A & B is category. I want that If C is a subcategory of A then no other C data is not written in A. But C can be written in B category.
I also want that If a User SoftDeletes the data then he can insert same data with the same name and with the same category.
My Table is
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->integer('parent_id')->nullable()->default(0);
$table->integer('admin_id')->nullable()->default(0);
$table->boolean('active')->nullable()->default(1);
$table->unique(array('parent_id', 'name', 'deleted_at'));
$table->rememberToken();
$table->timestamps();
$table->softDeletes();
});
My Store method is
public function store(Request $request)
{
$this->validate($request, [
'category' => 'required|unique:categories,parent_id,NULL,name,NULL,id,deleted_at,NULL',
'parent' => 'required',
'_token' => '',
]);
try {
$category = New Category();
$category->name = $request['category'];
$category->parent_id = $request['parent'];
$category->remember_token = $request['_token'];
$category->save();
$request->session()->flash('alert-success', 'Sub Category Successfully Created!');
return redirect('subcategory');
} catch(\Illuminate\Database\QueryException $e) {
return redirect()->back()
->with('status','<strong>'.$category.' already exist!</strong>');
}
}
My model is
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Category extends Model
{
use Notifiable;
use SoftDeletes;
protected $fillable = [
'category',
];
protected $dates = ['deleted_at'];
}
The Error is
Try
‘category' => 'required|unique:categories,parent_id,NULL,id,deleted_at,NULL,name,’.$request['category'],
For more details, separate the parameters into pairs:
[categories, parent_id]
This means the unique rule should be implemented in the table categories, column parent_id.
If the position of parent_id is same with the left “category”, it can be ignore. E.g
‘name’ => ‘unique:products,name’ can be simplified to
‘name’ => ‘unique:products’.
[NULL, id]
The unique rule should exclude the item where id is null.
It looks like nonsense, because id cannot be null.
But this pair is a special one. In the Laravel unique validation (vendor/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php:getUniqueIds()), it defines parameters the third position a special one. As long as you want to attach other constraints after, you need this. Just like a format.
Once again, this is a special pair. In the update validation, you can pass in the id to ignore the updated item itself.
[deleted_at, NULL]
The unique rule should implement in the scope where deleted_at is null.
The is where you consider the soft delete.
You can use NOT_NULL to mean the opposite.
Therefore, the final scope to implement the unique rule is the results get from below query:
Select count(*) from categories
where id is not null
and deleted_at is null
and name is ??$request['category']??
If the count(*) is 0, then the validation get through. Otherwise, fail.
You can have as many constraints as you need after the third pair. such as:
=> 4. [active, true]
=> 5. [name, hello], you can use '!hello' to mean where the name is not 'hello'
Could it be you validation string is not right?
Shouldn't it be
required|unique:categories,parent_id,NULL,name,NULL,deleted_at,NULL

Categories