Currently got some downtime and thought I’d play around with laravel as I usually just use it for basic web dev.
I’ve tried seeders and like how it’s quicker for me to drop test data into a table but I’m not sure how I go about doing this for multiple tables which have relationships.
In my case let’s say I’ve got 3 tables, a customer table, a customer address table and a customer purchase table.
Seeding the customer table is easy because it’s the main table but the other two tables have a column called customer_id which is the reference back to the customer table.
If I want to use seeders how do I write the code to make X amount of customers and then make the following records in the other tables which link back the created customers.
Apologies if I’ve missed something in the documentation.
First, you need to set up the relationship on the Customer model. I assume the Customer model has one-to-many relationship with CustomerAddress and CustomerPurchase models.
<?php
// app/Customer.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
public function addresses()
{
return $this->hasMany(CustomerAddress::class);
}
public function purchases()
{
return $this->hasMany(CustomerPurchase::class);
}
}
Next, you're going to need to define a model factory.
<?php
// database/factories/CustomerFactory.php
use Faker\Generator as Faker;
$factory->define(App\Customer::class, function (Faker $faker) {
return [
'name' => $faker->name,
];
});
$factory->define(App\CustomerAddress::class, function (Faker $faker) {
return [
'address' => $faker->streetAddress,
];
});
$factory->define(App\CustomerPurchase::class, function (Faker $faker) {
return [
'item' => $faker->word,
'amount' => $faker->randomFloat(2, 10, 200),
];
});
Now all you have to do is use those model factories to seed random records on the database. You can still use the Eloquent collection and relationship just fine:
<?php
// database/seed/DatabaseSeeder.php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
factory(App\Customer::class, 10)->create()->each(function ($customer) {
$customerAddresses = factory(App\CustomerAddress::class, 2)->make();
$customerPurchases = factory(App\CustomerPurchase::class, 5)->make();
$customer->addresses()->saveMany($customerAddresses);
$customer->purchases()->saveMany($customerPurchases);
});
}
}
Hope this gives you some ideas.
Some modified code from documentation:
$users = factory(Customer::class, 3)
->create()
->each(function ($u) {
$u->addresses()->save(factory(Address::class)->make());
$u->purchases()->saveMany(factory(Purchase::class, 10)->make());
});
You can simple make a foreach statement
$customers = factory(Customer::class,N)->create();
foreach( $customers as $customer)
{
factory(CustomerAddress::class)->create([
'customer_id' => $customer->id
]);
factory(CustomerPurchase::class)->create([
'customer_id' => $customer->id
]);
}
NOTE This is recommended only in case you want to have an address and purchase for every customer.
Else you should treat the seeding differently and not provide an address and purchase for every customer.
This will make your tests less reliable
Related
I want to get the notifiable model relation while getting notifications for a specific user. And plus I want to save and get custom morph relation (i.e. causer (causer_id and causer_type)) in notification table (just like notifiable). I was able to create a morph relation and saving it into table record but I am having trouble while getting the relation model, it returns null in both relations. Sharing the code.
custom DatabaseChannel -- the modified buildPayload method.
protected function buildPayload($notifiable, Notification $notification)
{
$data = $this->getData($notifiable, $notification);
$causer = $data['causer'];
unset($data['causer']);
return [
// README: removed uuid from here
'type' => method_exists($notification, 'databaseType')
? $notification->databaseType($notifiable)
: get_class($notification),
'data' => $data,
'read_at' => null,
'causer_type' => get_class($causer),
'causer_id' => $causer->id,
];
}
custom notifiable trait
use Illuminate\Notifications\Notifiable as BaseNotifiable;
trait Notifiable
{
use BaseNotifiable;
/**
* Get the entity's notifications.
*/
public function notifications(): MorphMany
{
return $this->morphMany(Notification::class, 'notifiable')
->orderBy('created_at', 'desc');
}
}
and one thing more I'd like to ask here, how can I refer to two morph relations to single table, like I want to add notifications method with causer and as well as notifiable.
custom Notifications Model
class Notification extends DatabaseNotification
{
public function causer()
{
return $this->morphTo();
}
}
what I am missing or doing wrong? Is it even possible what I am trying to do?
In order to get morph relation (one or maybe two), you need to select columns {morph}_id and {morph}_type, this is just in case if you are using ->select() while getting records, if you are not using select, that'd not be doing any issue.
EDIT
Here is how you add custom columns to notifications table
public function up()
{
Schema::table('notifications', function (Blueprint $table) {
$table->string("causer_type")->after('notifiable_id');
$table->unsignedInteger("causer_id")->after('causer_type');
$table->index(["causer_type", "causer_id"]);
});
}
public function down()
{
Schema::table('notifications', function (Blueprint $table) {
$table->dropMorphs('causer');
});
}
I am trying to make a testcase within laravel.
I have a fake User model (which dosent exists in DB), and creating it using faker->make,
and a real Role model which exists in DB,
these two have a many-to-many relationship
in my testcase, i am going to associate them like here :
public function testAccess()
{
$user = factory(\App\User::class)->make();
$supervisionControllerRole = \App\Role::where('name', 'supervision_controller')->first();
$user->roles->add($supervisionControllerRole);
}
since i dont want to save the relation in database, i am using add() instead of attach():
$user->roles()->attach($supervisionControllerRole->id);
//resulting database modification.
Problem
my problem is, when i am trying to get the relation from the model its ok.
var_dump($user->roles->first());
but when i am trying to get the relation Within The Model, it dosent works.
like here in my User Model:
public function hasRole($roleName)
{
$role_id = Cache::tags(['role_id'])->remember($roleName, 24*3600, function () use ($roleName) {
return \App\Role::where('name', $roleName)->first()->id;
});
return $this->roles()->where('role_id', $role_id)->exists();
}
It will returns false and trying $this->roles->count() results 0
from inside of the model.
My definitions
in User model:
public function roles()
{
return $this->belongsToMany("App\Role", "role_user")->whereNull("deleted_at")->using("App\RoleUser");
}
User Factory:
$factory->define(User::class, function (Faker $faker) {
return [
'id' => $faker->randomNumber(),
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => Str::random(80), // password
'remember_token' => Str::random(10),
];
});
Whenever you call a relationship with parentheses, such as
return $this->roles()->where('role_id', $role_id)->exists();
^^
you're accessing a Builder query instance which will return info from the database. But your data is not in the database, so of course it won't find anything when it looks there.
When you directly add() the relationship (vs attach()), you're inserting into a Collection instance, which as you know doesn't affect the database. This information is saved on the model only and stored in memory. Hence when you do
var_dump($user->roles->first());
it finds the information since it's already in memory. (You should also be able to call $user->roles->count() here and get a non-zero value.)
And since it's in a relationship of the model vs a direct attribute, I don't even think it would update the database if you were to save() the model.
You can use the contains method to perform the first step if you are not storing in the database:
return $this->roles->contains($role_id);
I am building a training web application to track associate training and certifications. I have created an Associate model that used a secondary DB connection to another database that has its information generated by another application so I have no control over the structure of the data. The associates table uses the associate's number as primary key and not an auto incremented ID. I have created a table to keep track of every training/certification course that take place. I created a many to many relationship between the Associate and the Course but when trying to add a record to the pivot table I am running an error.
"SQLSTATE[42S02]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid object name 'associate_course'. (SQL: insert into [associate_course] ([associate_id], [course_id], [created_at], [updated_at]) values (0000, 1, 2020-01-31 18:36:56.390, 2020-01-31 18:36:56.390))",
Here is the function that is called to create a record in the pivot table (where the error occurs)
public function trained(Course $course, Request $request) {
$request->validate([
'associates' => 'required'
]);
$associates = explode(',', $request->associates);
foreach($associates as $associate_number) {
$associate = Associate::where('NUMBER', $associate_number)->first();
$course->associates()->attach($associate);
}
}
Here is my Associate model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Associate extends Model
{
protected $connection = 'SHOPFLOOR';
protected $table = 'USERS';
public function getRouteKeyName()
{
return 'NUMBER';
}
public function courses()
{
return $this->belongsToMany(Course::class, 'associate_course', 'associate_id', 'course_id', 'NUMBER', 'id')->withTimestamps();
}
}
and here is my Course model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Course extends Model
{
protected $fillable = ['course_type_id', 'name', 'date', 'expires', 'expires_at', 'notification_threshold'];
public function type() {
return $this->belongsTo(CourseType::class);
}
public function associates() {
return $this->belongsToMany(Associate::class, 'associate_course', 'course_id', 'associate_id', 'id', 'NUMBER')->withTimestamps();
}
}
I tried copying the sql from the error and running it on the database and it does insert the row into the database so that leads me to believe it's something with my Laravel configuration?
Can I get some assistance in fixing this issue?
It's hard to say without seeing your DB structure, Error says clearly that table specified in query is not found. Most probably is that you forgot to specify schema in the statement. IMHO code that you showed is not really related to the problem.
There is a bug with the Laravel sqlsrv driver. I needed to specify the full schema to get it working. In my case this was TRAINING.dbo.associate_course.
So relationships look like this
Associate.php
public function courses()
{
return $this->belongsToMany(Course::class, 'TRAINING.dbo.associate_course', 'associate_id', 'course_id', 'NUMBER', 'id')->withTimestamps();
}
Course.php
public function associates() {
return $this->belongsToMany(Associate::class, 'TRAINING.dbo.associate_course', 'course_id', 'associate_id', 'id', 'NUMBER')->withTimestamps();
}
I wan't to store updates in pivot tables inside a separate table called audits_pivot.
To do that I need to sort of hook into the attached event on the model (State), which as I found out doesn't really exist. What I can do is to listen on the custom pivot class (LicenceState) for static::saving to be called, since that is the equivalent to 'attached'. Unfortunately does the callback of static::saving not contain any information about what the pivot was attached to.
There are libraries like this one from fico7489, but that doesn't work together with Laravel Nova, which I'm using.
How can I access things like the name and Id of the Model that the pivot row was attached to?
<?php
namespace App;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\Pivot as EloquentPivot;
use OwenIt\Auditing\Auditable as AuditableTrait;
use OwenIt\Auditing\Contracts\Auditable;
abstract class Pivot extends EloquentPivot implements Auditable
{
use AuditableTrait;
public static function boot()
{
parent::boot();
static::saving(function ($model) {
// How can I access here things like the name and Id of the Model that the pivot row was attached to?
// What I'm looking for is basically this:
// savePivotAudit('attached', 12, 'App\Licence', 'App\State', 51, '2020-01-14 13:55:58');
});
}
private function savePivotAudit($eventName, $id, $relation, $pivotId, $date)
{
return app('db')->table('audits_pivot')->insert([
'event' => $eventName,
'auditable_id' => $id,
'auditable_type' => $this->getMorphClass(),
'relation_id' => $pivotId,
'relation_type' => $relation,
'parent_updated_at' => $date,
]);
}
}
class License extends EloquentModel {}
class State extends EloquentModel
{
use AuditableTrait;
public function licenses()
{
return $this->belongsToMany(License::class)
->using(LicenseState::class);
}
}
class LicenseState extends Pivot {}
The Accountant package does what you want.
It supports many to many relations (i.e. pivot tables), by using Eventually, which adds events for attach(), detach(), updateExistingPivot(), sync() and toggle().
There's not even a need for using custom intermediate models.
The documentation covers all aspects of installation, configuration and usage.
I have a phone_models, phone_problems, and a phone_model_phone_problem pivot table. The pivot table has an extra column 'price'.
PhoneModel:
class PhoneModel extends \Eloquent
{
public function problems()
{
return $this->belongsToMany('RL\Phones\Entities\PhoneProblem')->withPivot('price');
}
}
PhoneProblem:
class PhoneProblem extends \Eloquent
{
public function models()
{
return $this->belongsToMany('PhoneModel')->withPivot('price');
}
}
What I'm trying to do is get the price of a specific phone with a specific problem.
This is how I have it now but I feel like Laravel has a built in Eloquent feature I can't find to do this in a much simpler way:
$model = $this->phoneService->getModelFromSlug($model_slug);
$problem = $this->phoneService->getProblemFromSlug($problem_slug);
all this does is select the specific model and problem from their slug.
then what I do is with those credentials I get the price like so:
$row = DB::table('phone_model_phone_problem')
->where('phone_model_id', '=', $model->id)
->where('phone_problem', '=', $problem->id)
->first();
so now I can get the price like so $row->price but I feel like there needs to be a much easier and more 'Laravel' way to do this.
When using Many to Many relationships with Eloquent, the resulting model automatically gets a pivot attribute assigned. Through that attribute you're able to access pivot table columns.
Although by default there are only the keys in the pivot object. To get your columns in there too, you need to specify them when defining the relationship:
return $this->belongsToMany('Role')->withPivot('foo', 'bar');
Official Docs
If you need more help the task of configuring the relationships with Eloquent, let me know.
Edit
To query the price do this
$model->problems()->where('phone_problem', $problem->id)->first()->pivot->price
To get data from pivot table:
$price = $model->problems()->findOrFail($problem->id, ['phone_problem'])->pivot->price;
Or if you have many records with different price:
$price = $model->problems()->where('phone_problem', $problem->id)->firstOrFail()->pivot->price;
In addition.
To update data in the pivot you can go NEW WAY:
$model->problems()->sync([$problemId => [ 'price' => $newPrice] ], false);
Where the 2nd param is set to false meaning that you don't detach all the other related models.
Or, go old way
$model->problems()->updateExistingPivot($problemId, ['price' => $newPrice]);
And remind you:
To delete:
$model->problems()->detach($problemId);
To create new:
$model->problems()->attach($problemId, ['price' => 22]);
It has been tested and proved working in Laravel 5.1 Read more.
Laravel 5.8~
If you want to make a custom pivot model, you can do this:
Account.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Account extends Model
{
public function users()
{
return $this->belongsToMany(User::class)
->using(AccountUserPivot::class)
->withPivot(
'status',
'status_updated_at',
'status_updated_by',
'role'
);
}
}
AccountUserPivot.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class AccountUserPivot extends Pivot
{
protected $appends = [
'status_updated_by_nice',
];
public function getStatusUpdatedByNiceAttribute()
{
$user = User::find($this->status_updated_by);
if (!$user) return 'n/a';
return $user->name;
}
}
In the above example, Account is your normal model, and you have $account->users which has the account_user join table with standard columns account_id and user_id.
If you make a custom pivot model, you can add attributes and mutators onto the relationship's columns. In the above example, once you make the AccountUserPivot model, you instruct your Account model to use it via ->using(AccountUserPivot::class).
Then you can access everything shown in the other answers here, but you can also access the example attribute via $account->user[0]->pivot->status_updated_by_nice (assuming that status_updated_by is a foreign key to an ID in the users table).
For more docs, see https://laravel.com/docs/5.8/eloquent-relationships (and I recommend press CTRL+F and search for "pivot")