Laravel inserting parent child fails inside transaction - php

I am trying to perform 2 inserts, in 2 tables table are constrained via a foreign key. These 2 operations MUST be performed inside a transaction to prevent eventual failures. (In reality I need to perform more inserts on more tables so transactions are important; but the 2 tables in this example are enough to replicate the problem)
The database driver is pgsql
SomeRepo.php (Tried it with the the transaction closure variant as well)
DB::beginTransaction();
try {
$parentData = [
'name' => 'Parent name'
];
$parent = new Parent($parentData);
$parent->save();
$childData = [
// Tried it with and without setting "parent_id" here
'parent_id' => $parent->id,
'name' => 'Child name'
];
$child = new Child($childData);
$parent->children()->save($child);
DB::commit();
} catch (Exception $e) {
DB::rollback();
}
Parent.php
protected $fillable = [
'name'
];
public function children()
{
return $this->hasMany(Child::class);
}
Child.php
protected $fillable = [
'name', 'parent_id'
];
Execution fails when trying to insert the child row, with the return parent id.
insert or update on table "child" violates foreign key constraint "child_parent_id_foreign"
EDIT
Child table SQL:
DROP TABLE IF EXISTS "public"."child";
CREATE TABLE "public"."child" (
"id" int4 NOT NULL DEFAULT nextval('child_id_seq'::regclass),
"parent_id" int4 NOT NULL,
"is_read" bool NOT NULL DEFAULT false,
"created_at" timestamp(0) DEFAULT now(),
"updated_at" timestamp(0),
"deleted_at" timestamp(0)
)
;
ALTER TABLE "public"."child" OWNER TO "my_user";
-- ----------------------------
-- Primary Key structure for table child
-- ----------------------------
ALTER TABLE "public"."child" ADD CONSTRAINT "child_pkey" PRIMARY KEY ("id");
-- ----------------------------
-- Foreign Keys structure for table child
-- ----------------------------
ALTER TABLE "public"."child" ADD CONSTRAINT "child_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "public"."parent" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION DEFERRABLE INITIALLY DEFERRED;

To save data in related table use this
Parent::create(['name'=>'parent name']); //save in parent table
$lastId = Parent::query()->max('id'); //get last inserted row id
$parent = App\Parent::find($lastId);
$child = $parent->children()->create([
'message' => 'A new comment.',
]);
You can also use createMany method
$parent = App\Parent::find($lastId);
$parent->children()->createMany([
[
'message' => 'A new comment.',
],
[
'message' => 'Another new comment.',
],
]);

Try it with this code by using create and createMany on the relation of the Parent model:
// Create the parent object.
$parent = Parent::create([
'name' => 'Parent 1'
]);
// Insert one.
$child = $parent->children()->create([
'name' => 'Child 1',
]);
// Insert many.
$parent->children()->createMany([
[
'name' => 'Child 2',
],
[
'name' => 'Child 3',
],
]);

You can try this one
try {
DB::beginTransaction();
$parent = Parent::create([
'name' => 'Parent name'
]);
$parent->children()->create([
'parent_id' => $parent->id,
'name' => 'Child name'
]);
DB::commit();
} catch (Exception $e) {
DB::rollback();
}

alter the foreign key-- run this sql on the database
alter table child drop constraint child_parent_id;
alter table child add foreign key (parent_id) references parent(id) deferrable initially deferred;
This will allow you to create the child or parent in either order and the constraint will not be validated until the commit. It should not be necessary in this case-- however, It does depend on how your IDs are generated.

children function needs a middle table change it to :
public function children()
{
return $this->belongsToMany(Child::class);
}
and don't do this $parent->children()->save($child);.
or if you want to go that way make child_parent table, with 2 fields child_id and parent_id.

Related

Enabling null on many FKs to same associated table

I have multiple fields setup as FK to the same table. The FK can also be NULL.
I keep getting this error:
ExistsIn rule for 'late_agreement_exception_outcome_recommendation_id' is invalid. 'ExceptionOutcomes' is not associated with 'App\Model\Table\ExceptionsTable'.
Database structure:
exceptions
id,
request_id,
late_agreement_exception_outcome_recommendation_id (FK, exception_outcomes->id)
late_agreement_exception_outcome_id (FK, exception_outcomes->id),
...
exception_outcomes
id,
name
...
Column definition (ie. late_agreement_exception_outcome_recommendation_id):
Column relationship (ie. late_agreement_exception_outcome_recommendation_id):
ExceptionTable:
FK setup to ExceptionOutcomes
$this->belongsTo('LateAgreementExceptionOutcomeRecommendations', [
'class' => 'ExceptionOutcomes',
'foreignKey' => 'late_agreement_exception_outcome_recommendation_id',
'joinType' => 'INNER',
]);
Edited rules attempting to enable entry of a null value for the field value:
$rules->add(
function ($entity, $options) {
$rule = new ExistsIn('late_agreement_exception_outcome_recommendation_id', 'ExceptionOutcomes');
return $entity->late_agreement_exception_outcome_recommendation_id === NULL || $rule($entity, $options);
},
['errorField' => 'late_agreement_exception_outcome_recommendation_id']
);
Update #1
I changed the association name like so:
$rules->add(
function ($entity, $options) {
$rule = new ExistsIn('late_agreement_exception_outcome_recommendation_id', 'LateAgreementExceptionOutcomeRecommendations');
return $entity->late_agreement_exception_outcome_recommendation_id === NULL || $rule($entity, $options);
},
['errorField' => 'late_agreement_exception_outcome_recommendation_id']
);
And got the following issue simply saving the data:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'sdi_ips2.late_agreement_exception_outcome_recommendations' doesn't exist
Previously, I could save the column when providing a value. However, trying to revert to NULL would cause an issue.
Update #2
try
{
$this->Requests->save($request);
}
catch(Cake\Database\Exception\DatabaseException $e)
{
debug("here!");
exit;
}
Update #3
Here's what I see in the SQL log:
Generated Models
The following Table objects used Cake\ORM\Table instead of a concrete class:
LateAgreementExceptionOutcomeRecommendations
Solution:
Note the className attribute in belongsTo.
$this->belongsTo('LateAgreementExceptionOutcomeRecommendations', [
'className' => 'ExceptionOutcomes',
'foreignKey' => 'late_agreement_exception_outcome_recommendation_id',
'joinType' => 'INNER',
]);

CakePHP buildRules not firing on hasMany nested assocation delete

I am having issue with build rules not firing deleting 1 element from a HasMany association on a model in CakePHP 3. My base table (Settings) is structured with the following association:
<?php
// SettingsTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->hasMany('Mappings', [
'foreignKey' => 'mappings_id',
'saveStrategy' => 'replace'
]);
}
The associated mappings table has custom rule in it's model that checks for the existence of children in the values table, prior to letting a mapping be deleted:
<?php
// MappingsTable.php
public function initialize(array $config)
{
parent::initialize($config);
$this->hasMany('Values', [
'foreignKey' => 'values_id',
'saveStrategy' => 'replace'
]);
}
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->addDelete(/* Has children rule, prevents deletion if children are found in Values table */);
}
I am using the following to update the settings table, and delete a nested mapping table entry (id 3):
<?php
$requestData = [
'title' => 'A new updated title',
'mappings' => [
['id' => 1, 'sort_order' => '5'],
['id' => 2, 'sort_order' => '10'],
// mappings id 3 was previously defined, but removed to make patchEntity() / save() delete it
]
];
$settingsTable->patchEntity($settingEntity, $requestData, [
'associated' => ['Mappings', 'Mappings.Values']
]);
$settingsTable->save($settingEntity);
Based on what I read about setting the saveStradegy key to replace for the hasMany association in the cake docs here, it seems like this should work.
The delete on the mapping with id 3 does fire in cake using this code, but for some reason the buildRules() call in the 2nd code spinet never fires. $rules->addDelete() never fires if there are child values on a mapping, and cake is forced to default to show the MySQL foreign key constraint failure message, rather than a cleaner build rule failure:
"SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`db`.`values`, CONSTRAINT `values_fk` FOREIGN KEY (`field_id`) REFERENCES `fields` (`id`))"
What do I need to do to get the buildRules() on MappingsTable to fire when when a mapping is removed from a setting, like I am above?
Any help is appreciated!

Laravel inserting by relationship to composite key table

I have these tables currently:
User table
id (primary key), name, email
User Model
protected $fillable = ['name', 'email'];
protected $visible = ['id','name','email'];
//Relationship
public function customAttributes()
{
return $this->hasMany('App\Models\UserAttribute');
}
UserAttribute Table
user_id, attribute_id, value //user_id and attribute_id is a composite key, both foreignkeys acting as primary keys establishing an unique combination
UserAttribute Model
protected $fillable = ['user_id', 'attribute_id','value'];
protected $visible = ['user_id', 'attribute_id','value'];
I'll use the following example to explain the issue:
$user = $this->user->create(['name' => 'admin', 'email' => 'admin#admin.com']);
//This works
$user->customAttributes()->save(new \App\Models\UserAttribute(['user_id' => $user->id, 'attribute_id' => 1, 'value' => 'Just a custom1']));
//This does not work
$user->customAttributes()->create([new \App\Models\UserAttribute(['user_id' => $user->id, 'attribute_id' => 1, 'value' => 'Just a custom1'])]);
I could just repeat the save for every custom that I want since it works, but I'm trying to figure out why create doesn't work.
The error I'm getting when I use create is (and yes, I've checked the record exists in the table that isn't listed here):
Cannot add or update a child row: a foreign key constraint fails (`testdatabase`.`user_attributes`,
CONSTRAINT `user_attributes_attribute_id_foreign` FOREIGN KEY (`attribute_id`) REFERENCES `attributes` (`id`))
This is the query it's trying to execute:
insert into `user_attributes` (`user_id`) values (1)
I'm just curious at why this doesn't work with create, I'm not sure if it's something related to this specific scenario (create to a composite key table by relationship). It's somewhat ignoring the value and attribute_id field in the query that is executing
try this:
$user->customAttributes()->create(['user_id' => $user->id, 'attribute_id' => 1, 'value' => 'Just a custom1']);
customAttributes() already returns you instance of UserAttribute model, you don't need to enject that dependency when you use create() method via that relation
your query should be like below;
$user->customAttributes()->insert([
[
'user_id' => $user->id,
'attribute_id' => 1,
'value' => 'Just a custom1'
],
[
'user_id' => $user->id,
'attribute_id' => 2,
'value' => 'Just a custom2'
],
]);

yii migration return 1215 mysql error

After searching and checking my code over & over, now I want to ask.
I have 2 table umessage and ureply, when I want to add foreign key from ureply refrence to umessage I got 1215 mysql error.
Codes in file m140602_080318_create_table_umessage.php which create umessage table:
public function safeUp()
{
/*
* Create table umessage, this is connection way between customer & seller about specific object
* Add foreign key to table user and it's column id with sender column
* Add foreign key to table object and it's column id with objId column
*/
$this->createTable('tbl_umessage', array(
'id' => 'pk',
'time' => 'INT(15) NOT NULL',
'body' => 'TEXT NOT NULL',
'status' => 'TINYINT NOT NULL DEFAULT 0',
'visibleToS' => 'TINYINT NOT NULL DEFAULT 0',
'visibleToR' => 'TINYINT NOT NULL DEFAULT 0',
'sender' => 'INT(11)',
'objId' => 'INT(11) NOT NULL',
), 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci');
}
And codes in file m140602_080329_create_table_ureply.php which create ureply table:
public function safeUp()
{
/*
* Create table ureply which store all replies to exact message
* Add foreign key to table umessage and it's column id with msgId column
*/
$this->createTable('tbl_ureply', array(
'id' => 'pk',
'time' => 'INT(15) NOT NULL',
'body' => 'TEXT NOT NULL',
'isSender' => 'TINYINT NOT NULL DEFAULT 0',
'msgId' => 'INT(11) NOT NULL',
), 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci');
$this->addForeignKey('fk_ureply_umessage', 'tbl_ureply', 'msgId', 'umessage', 'id', 'CASCADE', 'CASCADE');
}
1215 error is for adding fk_ureply_umessage foreign key and I can't find my goofs.
Any help will be appreciated. thanks in advance.
You have a mistake in addForeignKey method:
$this->addForeignKey('fk_ureply_umessage', 'tbl_ureply', 'msgId', 'umessage', 'id', 'CASCADE', 'CASCADE');
the table that the foreign key references to should be tbl_umessage not umessage:
$this->addForeignKey('fk_ureply_umessage', 'tbl_ureply', 'msgId', 'tbl_umessage', 'id', 'CASCADE', 'CASCADE');
The error is related to foreign key references check the data type is equivalent between the foreign key and its reference

CakePHP bizarre saveAll issue. Returns true, but nothing in database

I have two models, Statement -> hasMany -> LineItem.
I'm passing the following array to $this->saveAll() inside one of Statement's methods:
$data = array(
'Statement' => array(
'employee_id' => 1
),
'LineItem' => array(
0 => array('job_id' => 1),
1 => array('job_id' => 9),
2 => array('job_id' => 12),
)
);
$this->saveAll($data) == true, but when I check the statements table in the database, it is empty. The strange bit? The auto_increment id column has been incremented by 1. This is also reflected by the fact that $this->Statement->id matches the current auto_increment of the table.
All of the above is true for the line_items table as well, only the auto_increment has been incremented by 3.
Update
When using $this->save($data), the Statement is saved correctly (of course, without the LineItem records). So, what's up with saveAll?
Update
So, I suppose the problem is somewhere in my controller or model. Some stupid doh! moment I'm sure. Here is the code from my controller and my model (comments and unrelated code removed for brevity).
First, the model:
class Statement extends AppModel {
public $belongsTo = array('CompanyStatement', 'Employee');
public $hasMany = array('LineItem');
public $name = 'Statement';
public function build_statement($data = NULL) {
if (!empty($data)) {
if ($this->saveAll($data)) {
$result = array(
'message' => 'Statement was saved.',
'element' => 'flash_success',
'success' => TRUE
);
} else {
$result = array(
'message' => 'There was a problem saving.',
'element' => 'flash_error',
'success' => FALSE
);
}
} else {
$result = array(
'message' => 'No data was received.',
'element' => 'flash_error',
'success' => FALSE
);
}
return $result;
}
}
And the controller:
class StatementsController extends AppController {
public $name = 'Statements';
public function create_new_statement() {
$result = $this->Statement->build_statement($this->data);
$this->Session->setFlash($result['message'], $result['element']);
$this->redirect(array(
'controller' => 'statements',
'action' => 'edit',
$this->Statement->id
));
}
public function edit($id = NULL) {
$this->set('statement' => $this->Statement->findById($id));
}
}
The LineItem model is very concise:
class LineItem extends AppModel {
public $name = 'LineItem';
public $belongsTo = array('Statement', 'Job');
}
Getting Somewhere
I turned off the redirect in the controller method, and took a look at the SQL dump:
Warning (512): SQL Error: 1452: Cannot add or update a child row:
a foreign key constraint fails (`database`.`line_items`,
CONSTRAINT `line_items_ibfk_1`
FOREIGN KEY (`statement_id`) REFERENCES `statements` (`id`)
ON DELETE CASCADE ON UPDATE NO ACTION)
[CORE\cake\libs\model\datasources\dbo_source.php, line 681]
Query: INSERT INTO `line_items` (`job_id`, `modified`, `created`) VALUES (1, '2010-11-02 15:22:39', '2010-11-02 15:22:39')
Query: INSERT INTO `line_items` (`job_id`, `modified`, `created`) VALUES (9, '2010-11-02 15:22:39', '2010-11-02 15:22:39')
Query: INSERT INTO `line_items` (`job_id`, `modified`, `created`) VALUES (10, '2010-11-02 15:22:39', '2010-11-02 15:22:39')
The transaction is being rolled back.
So why isn't the statement_id being added to the query in the transaction when using saveAll?
This happens to me every once in a while when I forget to commit transactions. Have you disabled autocommit? Are you using transactions? Did you forget to commit or tell cakePHP you needed to commit?
By default Cake will expect the foreign key on line_items to be statement_id. You haven't shown the table structures. Is there actually a column statement_id on line_items?
$this->saveAll();
won't work, instead try:
$this->Statement->saveAll();
/* or */
$this->LineItem->saveAll();

Categories