Laravel: seeding tables other than Users - laravel

UPDATE: I am going to include my full file replacing the partial view I had. The seeder for the User table works, but the one for the Groups table does not. I do have those tables produced by Sentry but I only created a Model for Groups that has nothing in it other than the declaration of the class. Don't know what else to include.
<?php
class DatabaseSeeder extends Seeder {
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Eloquent::unguard();
//User::create(array('email' => 'foo#bar.com'));
// $this->call('UserTableSeeder');
$this->command->info('User table seeded!');
}
}
class UserTableSeeder extends Seeder {
public function run()
{
User::create(array(
'username' => 'alvaro',
'permissions' =>'{"user":1}'
));
$this->command->info('User table seeded!');
}
}
class GroupTableSeeder extends Seeder {
public function run()
{
Group::create(array(
'name' => 'usuario',
'permissions' =>'{"user":1}'
));
$this->command->info('Group table seeded!');
}
}
But actually, the one I want is the Groups tables (I am on Sentry). Yes, I have created the Model for Group, as Group.php but I don't know how to define its contents. Sometimes I have seen on other occasions that it suffices with just defining the class, but here I dont know, it doesn't work that easily.
Just doing something like
class GroupTableSeeder extends Seeder
will not work as it says that such class does not exist.

The only thing I needed to do was to create a separate file having that name GroupTableSeeder.php
and include the code in there. For some reason, while UserTableSeeder can be inside a file called DatabaseSeeder and it works, it does not work for other tables.
class GroupTableSeeder extends Seeder {
public function run()
{
Group::create(array(
'name' => 'basicuser',
'permissions' =>'{"user.create" :-1,"user.delete" :-1,"user.view":1,"user.update":-1,"post.create":1}'
));
$this->command->info('Group table seeded!');
}
}

Related

What is the recommended approach to test a model from another database in laravel?

I am developping a new application using TDD for the first time.
One of the requirement is to reuse a table that already exists for another project.
There is two database, one for my new app, one for the already existing project.
I have a model created in my new applications that references that other model.
GainCode (new app) -> belongsTo Company (existing app)
My first idea was to create a factory for both of these model for my test, but then I realised that because I set the protected $connection property on my Company model, it actually inserts data in the real table during the tests.
I tried to add the following code in the model to change the connectiong if it is under test environment:
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
if (App::environment('testing')) {
$this->connection = 'sqlite';
}
}
However, I noticed this does not work because I have no migration file for a Company.
I then decided to remove the factory and use Company::first() in my test instead and factory.
Before
'company_id' => function() {
return Company::factory()->create()->ID;
}
After
class GainCodeFactory extends Factory
{
/**
* Define the model's default state.
*
* #return array<string, mixed>
*/
public function definition()
{
return [
'code' => $this->faker->unique()->randomNumber(5),
'description_fr' => $this->faker->sentence(5),
'description_en' => $this->faker->sentence(5),
'inactive' => $this->faker->boolean(),
'company_id' => function() {
return Company::first()->ID;
}
];
}
}
Unit test sample
<?php
namespace Tests\Unit;
use App\Models\Company;
use App\Models\GainCode;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class GainCodeTest extends TestCase
{
use RefreshDatabase;
/** #test */
public function it_belongs_to_a_company()
{
$company = Company::first(); // BEFORE: Company::factory()->create();
$gainCode = GainCode::factory()->create(['company_id' => $company->ID]);
$this->assertEquals($company->ID, $gainCode->company->ID);
}
}
What is the recommended way to do TDD with such requirement?
Should I revert back to using factory and maintain a migration file for that table?

Laravel 8: How to seed a pivot table in many to many relationship

I want to seed my tables via seeder/factory.
I want to seed a Pivot table, which connects the tables atg and debtor and is called atg_debtor, which hast an id, created_at, updated_at, atg_id, debtor_id.
When inserting data via Eloquent everything works fine. When I try to seed my db with Seeders/Factory it always tries to insert data to my atg table, which I don´t want. I want to seed the debtor table (that works fine) and the pivot table with a given atg_id and the dynamic debtor_id, which is created whilst seeding the debtor.
My Debtor Model:
<?php
namespace App\Models;
use App\Models\Atg;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Debtor extends Model
{
use HasFactory;
public function atg()
{
return $this->belongsToMany(Atg::class)->withTimestamps();
}
}
The DebtorSeeder:
<?php
namespace Database\Seeders;
use App\Models\Atg;
use App\Models\Debtor;
use Illuminate\Database\Seeder;
class DebtorSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
$debitor = Debtor::factory()
->count(1)
->hasAtg(1)
->create();
}
}
The DebtorFactory:
<?php
namespace Database\Factories;
use App\Models\Debtor;
use Illuminate\Database\Eloquent\Factories\Factory;
class DebtorFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Debtor::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'created_at' => now(),
'created_by_id' => '3',
'rechtsform' => 'Unternehmen',
'name' => $this->faker->company
];
}
}
Thank you!
so first you can check if in the database there is Atg, if no you can seed like you do, otherwise you can seed Debtor then save Atg in relations(->atg() is your relation name so improve it if i wrote wrong name) like so:
P.S. Also i removed ->count(1) from Debtor because by default it creates one item.
<?php
namespace Database\Seeders;
use App\Models\Atg;
use App\Models\Debtor;
use Illuminate\Database\Seeder;
class DebtorSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
$atg = Atg::first();
if($atg){
$debitor = Debtor::factory()
->create();
$debitor->atg()->save($atg);
}else {
$debitor = Debtor::factory()
->hasAtg(1)
->create();
}
}
}
Assuming you're seeding your database tables separately:
// Seed 50 Atg records
Atg::factory(50)->create();
// Seed 4 Debtor records
Debtor::factory(4)->create();
You could override the configure method in your Debtor factory to attach some Atg models once they have been created:
class DebtorFactory extends Factory
{
public function configure()
{
return $this->afterCreating(function (Debtor $debtor) {
// Once a Debtor record has been created
// Pick between 1 and 5 Atg records in a random order
// Associate the Atg and Debtor records
$debtor->atg()
->attach(Atg::inRandomOrder()->take(random_int(1, 5))->pluck('id'));
});
}
}
You can use hasAttached method to seed pivot table in Laravel 8
In your DatabaseSeeder class file.
Atg::factory()->hasAttached(
Debtor::factory();
)
->create();
That will help you each Atg create item with dynamic Debtor id in atg_debtor table. But it needs to connect in the model class binding.
class Debtor extends Model
{
use HasFactory;
public function atg()
{
return $this->belongsToMany(Atg::class)->withTimestamps();
}
}
Other Methods,
You can use Using Magic Methods to create seed data in Pivot Table
Atg::factory()->hasDebtor(1)
->create();
Here are the reference methods
https://laravel.com/docs/8.x/database-testing#pivot-table-attributes

laravel-8 user table seeder does not exist

I am trying to make a login from laravel 8 but at the begging I faced an error which I cannot find a solution. The UsersTablesSeeder is created but still the compiler cannot find it
Illuminate\Contracts\Container\BindingResolutionException
Target class [UsersTablesSeeder] does not exist.
at C:\xampp\htdocs\pary\vendor\laravel\framework\src\Illuminate\Container\Container.php:832
828▕
829▕ try {
830▕ $reflector = new ReflectionClass($concrete);
831▕ } catch (ReflectionException $e) {
➜ 832▕ throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
833▕ }
834▕
835▕ // If the type is not instantiable, the developer is attempting to resolve
836▕ // an abstract type such as an Interface or Abstract Class and there is
1 C:\xampp\htdocs\pary\vendor\laravel\framework\src\Illuminate\Container\Container.php:830
ReflectionException::("Class "UsersTablesSeeder" does not exist")
2 C:\xampp\htdocs\pary\vendor\laravel\framework\src\Illuminate\Container\Container.php:830
ReflectionClass::__construct("UsersTablesSeeder")
the following code shows DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Eloquent::unguard();
$this->call(UsersTablesSeeder::class);
}
}
this is my user table
<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use App\User;
class UsersTablesSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
User::create([
'name' => 'John Smith',
'email' => 'john_smith#gmail.com',
'password' => Hash::make('password'),
'remember_token' => str_random(10),
]);
}
}
I am following this link
Add namespace Database\Seeders; to your class. As said in laravel 8
Seeders and factories are now namespaced. To accommodate for these
changes, add the Database\Seeders namespace to your seeder classes. In
addition, the previous database/seeds directory should be renamed to
database/seeders:

error when attempting to seed pivot table using Laravel

I have just created a many-to-many relationship between the models Project and Features using Laravel however I receive the following error when attempting to run the seed script.
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'table.feature_projects' doesn't exist
The table in use is named features_project which is the default name given.
Inserting the seed data manually does return the relational data to the view as expected.
SEED
class FeaturesProjectTableSeeder extends Seeder
{
public function run()
{
$features_project = new \App\FeatureProject ([
'project_id' => '1',
'features_id' => '1'
]);
$features_project->save();
}
}
Project
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Project extends Model {
public function features() {
return $this->belongsToMany('App\Features')->withTimestamps();
}
}
Features
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Features extends Model
{
public function projects() {
return $this->belongsToMany('App\Project')->withTimestamps();
}
}
CONTROLLER
class ProjectController extends Controller
{
public function getProject($id)
{
$project = Project::where('id', $id)->get();
return view('other.project', ['project' => $project]);
}
}
ROUTE
Route::get('project/{id}', [
'uses' => 'ProjectController#getProject',
'as' => 'other.project'
]);
VIEWS
#foreach($project->features as $feature)
<dd class="col-sm-8">{{ $feature->name }}</dd>
#endforeach
Firstly some misunderstanding in naming, your table name should be feature_project, in many to many relationships the models are in alphabetical order. Secondly models are not plural, so your Features.php model should be named Feature.php. Which will resolve in Laravel using the table feature_project for the pivot and features for the model.
For your own sake, learn how Laravel name models and tables, else relationships are gonna be tricky. Which is described in the documentation.
You should not create pivot models, this is handled by assigning features to projects or vice versa. Therefor your seeder should look something like this, it could be you should assign some attributes to the projects and features before it will work.
class FeaturesProjectTableSeeder extends Seeder
{
public function run()
{
$features = factory(Feature::class)->create();
$projects = factory(Project::class)->create();
$projects->each(function (Project $project) {
$project->features()->saveMany($features);
});
}
}

Extended User Model in a Package

Could you help me understand the right way to extend existing models? I'm developing a package and want to do as much as possible separated from the main application.
I want to have all existing functionality of the User model, but only add a relation to another model. In my package User I can have several (hasMany) Article. Instead of adding a method to \App\User I created a new model in my package class User extends \App\User:
namespace Package\Sample;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
/**
* Class User
* #package Package\Sample
*/
class User extends \App\User
{
use HasApiTokens, Notifiable;
public function articles()
{
return $this->hasMany(Article::class);
}
}
To make it work I add the configuration for my package auth.php:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \Package\Sample\User::class,
],
],
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
My question: Is it a "best practice" to extend functionality of existing models?
I solved it by using the class_alias() php function.
In my package service provider I setup an alias of the model class defined for Authentication in /config/auth.php like this:
public function boot(){
class_alias(config("auth.providers.users.model"), 'ParentModel');
}
then I use ParentModel class where needed:
use ParentModel;
class Agent extends ParentModel {
...
}
Hope It makes sense for someone
that will prevent edits on the \Package\Sample\User::class. any new method needed will result in a package update.
why not declare a trait in your package containing your methods and use them in the App\User::class like what laravel is using.
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
I tried using the class_alias and although it works for basic usage, when things got more complicated my user class couldn't cut it. For example, my notifications where using the local package user type and not showing in app.
After doing more research I found using a trait IS the proper way like others have mentioned.
I found a comprehensive guide here: https://laravelpackage.com/08-models-and-migrations.html#approach-2-using-a-polymorphic-relationship
the gist:
Create a trait in your package:
Important here you can setup whatever the relationship you need depending on you db modeling.
// 'src/Traits/HasPosts.php'
<?php
namespace JohnDoe\BlogPackage\Traits;
use JohnDoe\BlogPackage\Models\Post;
trait HasPosts
{
public function posts()
{
return $this->morphMany(Post::class, 'author');
}
}
Add the use in whatever user classes it applies to in your app
// 'App\Models\User.php'
<?php
namespace App\Models;
use JohnDoe\BlogPackage\Traits\HasPosts;
class User extends Authenticatable
{
use HasPosts;
...
Lastly, you'll need to add an extra field in the db for the user_type where you are using the user_id.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddUserTypeToPostTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->tinyText('user_type')
->comment('User class type')
->nullable();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('user_type');
});
}
}
You need to Add config(['auth.providers.users.model' => Myname\Myproject\App\Models\User::class]); to the boot-method inside my package-service-provider.
And Create new Class in your package.
namespace Myname\Myproject\App\Models;
class User extends \App\User
{
public function roles(){
return $this->belongsToMany('Myname\Myproject\App\Models\Role', 'user_role', 'user_id', 'role_id');
}
}

Resources