Laravel 4, how to I create seed data to tables with relationships? - database

I have created a database with two tables, "goals" and "partgoals". The practial use is to make a savings goal (money) and have milestones along the way (partgoals). I want the partgoals obviously be linked to a specific goal. The relationships are created but I run into trouble when trying to create my seed data.
My goal is to set up two goals table like this (GoalsTableSeeder.php):
<?php
class GoalsTableSeeder extends Seeder {
public function run()
{
DB::table('goals')->delete();
$goals = array(
array(
'max' => 1850000,
'avgsav' => 3500,
'duedate' => date('2015-03-15'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
),
array(
'max' => 1100000,
'avgsav' => 5000,
'duedate' => date('2013-11-15'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
)
);
DB::table('goals')->insert( $goals );
}
}
And my partgoals table like this (PartgoalsTableSeeder.php):
<?php
class PartgoalsTableSeeder extends Seeder {
public function run()
{
DB::table('partgoals')->delete();
$partgoals = array(
array(
'id' => 1,
'milestone' => 100000,
'duedate' => date('2014-03-15'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
),
array(
'id' => 1,
'milestone' => 20000,
'duedate' => date('2013-06-15'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
),
array(
'id' => 2,
'milestone' => 400000,
'duedate' => date('2013-09-15'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
),
array(
'id' => 2,
'milestone' => 200000,
'duedate' => date('2014-10-15'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
)
);
DB::table('partgoals')->insert( $partgoals );
}
}
The migration table for "goals":
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateGoalsTable extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('goals', function(Blueprint $table)
{
$table->increments('id');
$table->integer('max');
$table->float('avgsav');
$table->date('duedate');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('goals');
}
}
The migration table for partgoals:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePartgoalsTable extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('partgoals', function(Blueprint $table)
{
$table->foreign('id')
->references('id')->on('goals')
->onDelete('cascade');
$table->increments('id');
$table->float('milestone');
$table->date('duedate')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('partgoals');
}
}
What am I doing wrong? I am new to Laravel (and Laravel 4).

I see a few problems with your code:
1) The way you create the foreign key
When assigning the Foreign key constraint, you should make that column unsignedInteger.
In the code below I will correct the mistake you made of naming two columns 'id'.
Schema::create('partgoals', function(Blueprint $table)
{
$table->increments('id');
$table->unsignedInteger('goal_id');
$table->foreign('goal_id')
->references('id')->on('goals')
->onDelete('cascade');
$table->float('milestone');
$table->date('duedate')->nullable();
$table->timestamps();
});
2) The way you seed your database
If you specify a foreign key, you should declare the value when creating the entry in the table seeder.
If you want to specify a NULL value, this can be done by allowing the column to accept such value (by default it doesn't). In this case, we should add ->nullable()->default(NULL)
Schema::create('partgoals', function(Blueprint $table)
{
$table->increments('id');
$table->unsignedInteger('goal_id')->nullable()->default(NULL);
$table->foreign('goal_id')
->references('id')->on('goals')
->onDelete('cascade');
$table->float('milestone');
$table->date('duedate')->nullable();
$table->timestamps();
});
Minor mistake
3) You are passing the 'id' => 1 twice in your seeder

When using increments in the query builder, that automatically makes that your primary key, auto-incremented, and unique. You can't have a foreign key also be your primary key unless it's a one-to-one relationship. That's just bad design though. Your schema should look something like below.
Schema::create('partgoals', function(Blueprint $table)
{
$table->increments('id');
$table->foreign('goal_id')
->references('id')->on('goals')
->onDelete('cascade');
$table->float('milestone');
$table->date('duedate')->nullable();
$table->timestamps();
});
Also, when seeding, if you use the insertGetId when inserting, it will return the ID of the record you just inserted. This you can use in another insert, like inserting into another table, later. However, this has to take place in the same script. You may be able to pass it back out to DatabaseSeeder and then back into another seed script, but I haven't tried this.

I'm not familiar with Laravel, or what you're trying to do, but based on the error you added in the comments it seems that your problem is a result of trying to enter multiple records with the same primary key (1) into your partgoals table.
I'm not sure how you've set your tables up, but it seems like you've defined a partgoals table with a unique primary key column ID, which you're also trying to use as a foreign key to reference the goals table. It may be worth creating another field to hold your foreign key in the partgoals table.

To seed tables with relationship, you need to defined model factories in the ModelFactory.php file and then create a seeder class to run the seeder.
For ex. ModelFactory.php
$factory->define(App\Category::class, function (Faker\Generator $faker) {
$name = $faker->name;
return [
'name' => $name,
'visible' => 1
];
});
$factory->define(App\Video::class, function (Faker\Generator $faker) {
return [
'title' => $faker->name,
'description' => '',
'status' => 1
];
});
Then the seeder class can be as follows
<?php
use Illuminate\Database\Seeder;
class CategoriesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
$categories = factory(App\Category::class, 20)->create()->each(function ($u) {
for ($i=0; $i<5; $i++)
{
$u->videos()->save(factory(App\Video::class)->make());
}
});
}
}
You can refer this article on how to generate seeds for two tables with relationship http://deepdivetuts.com/seeding-two-tables-using-laravel-5-3

Related

Could not view with composite pK CakePHP4

In cakephp-4.x I could not access Controller's view action for composite primary key table. (http://localhost:8765/invoice-items/view/1)
Here are samples of the code created by cake bake:
InvoiceItemsTable calss in which the primary key is defined as composite.
class InvoiceItemsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('invoice_items');
$this->setDisplayField(['item_id', 'invoice_id', 'unit_id']);
$this->setPrimaryKey(['item_id', 'invoice_id', 'unit_id']);
$this->addBehavior('Timestamp');
$this->belongsTo('Items', [
'foreignKey' => 'item_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Invoices', [
'foreignKey' => 'invoice_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Units', [
'foreignKey' => 'unit_id',
'joinType' => 'INNER',
]);
}
...
InvoiceItemsController view method:
/**
* View method
*
* #param string|null $id Invoice Item id.
* #return \Cake\Http\Response|null|void Renders view
* #throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$invoiceItem = $this->InvoiceItems->get($id, [
'contain' => ['Items', 'Invoices', 'Units'],
]);
$this->set(compact('invoiceItem'));
}
Finally a screen shot of invoice_items table structure from phpmyadmin:
I have tried to access the view like (http://localhost:8765/invoice-items/view/1,1,6) but I got the same error ... with primary key ['1,1,6']. I do not know how to represent the composite primary key in the URL? or what is the problem?
I use CakePHP version 4.4.2
Table::get() expects composite keys to be passed as arrays, eg [1, 1, 6].
Assuming you're using the fallback routes from the default app skeleton, you can pass additional arguments as path parts, eg:
/invoice-items/view/1/1/6
and accept them in your controller action like:
public function view($itemId, $invoiceId, $unitId)
and build an array from the accordingly to pass to get() as the "id":
$this->InvoiceItems->get([$itemId, $invoiceId, $unitId], /* ... */)
In case you're using custom routes with fixed parameters, add additional ones in whatever form you like, for example with dashes:
$routes
->connect(
'/invoice-items/view/{itemId}-{invoiceId}-{unitId}',
['controller' => 'InvoiceItems', 'action' => 'view']
)
->setPass(['itemId', 'invoiceId', 'unitId'])
->setPatterns([
'itemId' => '\d+',
'invoiceId' => '\d+',
'unitId' => '\d+',
]);
then your URL would look like:
/invoice-items/view/1-1-6
See also
Cookbook > Routing > Route Elements
Cookbook > Routing > Passing Parameters to Action
Cookbook > Routing > Fallbacks Method

Is there a way to prevent CakePHP to cascade delete the associated belongsToMany relation

I use CakePHP 4 with a plugin called softdelete trait (the forked version for cake 4).
I have a simple many-to-many relation between virtual classrooms and learners.
Furthermore, I made a pivot table called learners_virtual_rooms to link the two.
Now when I edit my classroom, I can add and remove learners.
The issue comes when I try to delete the classroom while there are still learners linked :
The room is soft deleted, the rows in the pivot table are deleted, so far so good,
and then my learners are deleted too !
(at least try to be deleted because cake targets a column that doesn't exist in the learners table : virtual_room_id)
I tried using dependent options on my relations, removing the softdelete plugin, using hasMany relations to no avail.
Next is my code, feel free to ask more if it's not enough.
The virtual_rooms table
class VirtualRoomsTable extends Table
{
use SoftDeleteTrait;
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('virtual_rooms');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
//[...]
$this->belongsToMany('Learners');
//[...]
}
//[...]
}
The learners table
class LearnersTable extends Table
{
use SoftDeleteTrait;
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('learners');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
// [...]
$this->belongsToMany('VirtualRooms');
// [...]
}
//[...]
}
The delete method in my controller
public function delete(string $virtual_room_id): Response
{
Assert::true($this->request->is(['delete', 'post']), __('Méthode http incorecte'));
$virtual_room = $this->VirtualRooms->get($virtual_room_id);
if ($this->VirtualRooms->delete($virtual_room)) {
return $this->setJsonResponse([
'message' => __('Classe virtuelle supprimée avec succès'),
'success' => true,
]);
}
return $this->setJsonResponse([
'message' => __('La classe virtuelle n\'a pas pu être supprimée'),
'errors' => $virtual_room,
'validationErrors' => $virtual_room->getErrors(),
'success' => false,
]);
}
No file for the pivot table.
The stacktrace of the error look like that :
Cake\Database\Query->execute
ROOT\vendor\salines\cakephp4-soft-delete\src\Model\Table\SoftDeleteTrait.php:120
App\Model\Table\LearnersTable->deleteAll
CORE\src\ORM\Association\BelongsToMany.php:617
Cake\ORM\Association\BelongsToMany->cascadeDelete
CORE\src\ORM\AssociationCollection.php:343
Cake\ORM\AssociationCollection->cascadeDelete
ROOT\vendor\salines\cakephp4-soft-delete\src\Model\Table\SoftDeleteTrait.php:84
App\Model\Table\VirtualRoomsTable->_processDelete
CORE\src\ORM\Table.php:2281
The queries that cause the exception :
This one is ok
DELETE FROM
learners_virtual_rooms
WHERE
virtual_room_id = 31
This one is the culprit
UPDATE
learners
SET
deleted = '2021-03-23 14:44:03'
WHERE
virtual_room_id = '31'
debug($this->VirtualRooms->Learners->junction()) :
APP/Controller\Api\VirtualRoomsController.php (line 119)
object(Cake\ORM\Table) id:0 {
'registryAlias' => 'LearnersVirtualRooms'
'table' => 'learners_virtual_rooms'
'alias' => 'LearnersVirtualRooms'
'entityClass' => 'Cake\ORM\Entity'
'associations' => [
(int) 0 => 'Learners', (int) 1 => 'VirtualRooms',
]
I'm thinking of removing by hand all the learners before deleting the classroom, is it the only way ?
Many thanks for your time and help.

Laravel belongsToMany save does not respect primary key

I have self-relating table:
Schema::create('regions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('geoname')->unique();
$table->enum('type', ['continent', 'region', 'country', 'state', 'city'])->index();
$table->string('code', 2)->nullable()->index();
$table->string('name');
$table->string('language', 2)->nullable()->index();
$table->unsignedBigInteger('population')->nullable();
$table->timestamps();
$table->unique(['code', 'type']);
});
Schema::create('regions_has_regions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('region_id');
$table->unsignedBigInteger('has_region_id');
$table->timestamps();
$table->foreign('region_id')->references('geoname')->on('regions');
$table->foreign('has_region_id')->references('geoname')->on('regions');
});
I want to use geoname as primary key (as it is a globally unique integer identifier)
Model has:
protected $primaryKey = 'geoname';
public function children(): BelongsToMany
{
return $this->belongsToMany(
Region::class,
'regions_has_regions',
'region_id',
'has_region_id',
'geoname',
'geoname',
'children');
}
public function parents(): BelongsToMany
{
return $this->belongsToMany(
Region::class,
'regions_has_regions',
'has_region_id',
'region_id',
'geoname',
'geoname',
'parents');
}
Primary and relation keys are set to geoname.
But here I import countries in continents:
foreach ($continents as $continent) {
$pop = 0;
$continent->children()->saveMany(
collect($earth->find(['continent' => $continent->code])->useShortNames()->toArray())
->map(function ($c) use ($continent, $now, &$pop) {
$pop += $c['population'];
return Region::create([
'code' => $c['isoCode'],
'name' => $c['name'],
'type' => 'country',
'language' => $c['language'],
'population' => $c['population'],
'geoname' => $c['geonamesCode'],
]);
})
);
$continent->update(['population' => $pop]);
}
And get an error:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`homestead`.`regions_has_regions`, CONSTRAINT `regions_has_regions_has_region_id_foreign` FOREIGN KEY (`has_region_id`) REFERENCES `regions` (`geoname`)) (SQL: insert into `regions_has_regions` (`has_region_id`, `region_id`) values (7, 6255146))
As you can see it tries to insert id value to has_region_id, instead of geoname.
How to resolve this?
Ok, figured out: Model should have this:
public $incrementing = false;

Laravel 5 Saving the Selected input from a Form::Select

I a struggling to save the chosen information from my selection box. I get the message:
"SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (clientpad_notes.notebooks, CONSTRAINT notebooks_contact_id_foreign FOREIGN KEY (contact_id) REFERENCES contacts (id) ON DELETE CASCADE) (SQL: insert into notebooks (name, contact_id, note_description, note_body, user_id, updated_at, created_at) values (assddsaasdadasasd, 3, saS, ssss, 2, 2018-03-03 19:18:51, 2018-03-03 19:18:51)
My problem is that I am bringing in information to notes create page from contacts table. So I have my tables linked with authentication with user_id and inside the notes tables I have contacts_id. I am aiming to select and save a contact name fetching it by it's id, when creating a new note. It is possible I have been going around doing this in a wrong way, I am a beginner at Laravel so any help would be appreciated.
Here is my notes controllers and create a note page.
NotesController.php
public function create()
{
$user_id = auth()->user()->id;
$user = User::find($user_id);
$contacts = Contact::find($user->contacts)->pluck('fullName');
return view('notebooks.create')->with('contacts', $contacts)->with('user', $user);
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->validate($request,[
'name' => 'required',
'contact_id' => 'required',
'note_description' => 'required',
'note_body' => 'required',
]);
//Create a note
$notebook = new Notebook;
$notebook->name = $request->input('name');
$notebook->contact_id = $request->input('contact_id');
$notebook->note_description = $request->input('note_description');
$notebook->note_body = $request->input('note_body');
$notebook->user_id = auth()->user()->id; //currently logged in user show their notes
$notebook->save();
return redirect('/dashboard')->with('success', 'Your Note Was Created');
}
create.blade.php
<div class="col-6 col-sm-3">
{{Form::label('contact_id', 'Choose your Contact')}}
{{Form::select('contact_id', $user->contacts->pluck('fullName'), $contacts, ['class' => 'form-control'])}}
</div>
</div>
In the database which saved notes I have this:
Schema::create('notebooks', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->integer('contact_id')->unsigned()->nullable();
$table->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
$table->string('name');
$table->mediumText('note_description');
$table->mediumText('note_body');
$table->timestamps();
});
You made small mistake when working with Id's from other tables. You should check if the ID actually exists in the target table.
$this->validate($request,[
'name' => 'required',
'contact_id' => 'required|exists:contacts', //This will validate that the contact ID actually exists.
'note_description' => 'required',
'note_body' => 'required',
]);
//Create a note
$notebook = new Notebook;
$notebook->name = $request->input('name');
$notebook->contact_id = $request->input('contact_id');
$notebook->note_description = $request->input('note_description');
$notebook->note_body = $request->input('note_body');
$notebook->user_id = auth()->user()->id; //currently logged in user show their notes
$notebook->save();
Laravel validation#rule-exists
Update
$this->validate($request,[
'name' => 'required',
'contact_id' => 'required|exists:contacts,id', //check contacts table, for column ID
'note_description' => 'required',
'note_body' => 'required',
]);
SO after lots of searching I found a solution. In Create I had to use mapping:
public function create(Contact $contact)
{
$user_id = Auth::user()->id;
$user = User::find($user_id);
$contacts = $user->contacts->mapWithKeys(function($contact){
return [$contact->id => $contact->fullName];
});
// dd ($contacts);
return view('notebooks.create')->with('contacts', $contacts)->with('user', $user);
}
Then in Select I just called for this:
{{Form::label('contact_id', 'Choose your Contact')}}
{{Form::select('contact_id', $contacts, null, ['class' => 'form-control'])}}
Works like a charm, in case anyone else finds this useful

Seeder not working laravel 5.2

So consider the following err:
[PDOException]
SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column "created_at" violates not-null constraint
DETAIL: Failing row contains (5, Sample Name xxx#gmail.com, xxxxxx, null, null, null).
This is the seeder:
<?php
use Illuminate\Database\Seeder;
class AdminUser extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
DB::table('users')->insert([
'name' => 'Sample Name',
'email' => 'xxxx#gmail.com',
'password' => 'xxxxxx',
]);
}
}
The user model:
class Users extends Model {
protected $table = 'users';
protected $timestamps = true;
protected $fillable = ['name', 'email', 'password', 'created_at', 'updated_at'];
}
Whats going on? The migration, default from laravel install:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password', 60);
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('users');
}
}
Did they miss something? Did I?
Eloquent models automatically inserts timestamps for you, but the query builder doesn't. As the error message says, they can't be null. You have two options:
Add the timestamps manually:
DB::table('users')->insert([
'name' => 'Sample Name',
'email' => 'xxxx#gmail.com',
'password' => 'xxxxxx',
'updated_at' => new \Carbon\Carbon,
'created_at' => new \Carbon\Carbon
]);
Or use your User model to seed the database, and it will handle adding the timestamps for you.
User::create([
'name' => 'Sample Name',
'email' => 'xxxx#gmail.com',
'password' => 'xxxxxx'
]);

Resources