【symfony】Cannot insert a value for auto-increment primary keyの解消法
こんにちはこんばんはシステムエンジニアやってますcubeです。
弊社のアプリケーションは大体symfony1.4のpropelで作られていまして、実稼働しているアプリケーションでの管理画面もsymfony1.4のpropelで作りました。
symfonyではadmin-generateと呼ばれる管理画面が以下のタスク実行で簡単に作られるのでとてもお手軽ですね。
1 2 |
[sourcecode lang="text"] php symfony generate:app backend php symfony propel:generate-admin backend テーブル名 --module=作成されるモジュール名 [/sourcecode] |
ymfonyの公式ページなんかが詳しく解説されていますね
第14章 – Admin ジェネレーター
そして今回は
1 |
[sourcecode lang="text"] Cannot insert a value for auto-increment primary key (◯◯) [/sourcecode] |
の解消法。
これはsymfonyのアプリケーションからauto-increment指定されているprimary keyを指定して登録しようとした時に出力されるPropelExceptionでのエラー。
symfonyというよりもpropelのエラーですね。 このエラーをgoogle検索した時に出てくる記事は、弊社システムエンジニアkazさんの以下の記事
symfonyではまったDBのID変更~備忘録~
ほうほう、こんな感じで解消できるのねーと理解はしたものの、数十テーブル一つ一つに書いて行くのはちょっとキツいかもかも.. オーバーライドするのも面倒なので、勝手にbuildしないようにする方法は無いのかな? という面倒臭い精神でbuildするタスクを読んで見る事に。 まずbuildしてるのどこなのーってことでgrep。
1 |
[sourcecode lang="text"] $ grep -r 'Cannot insert a value for auto-increment primary key' lib/vendor lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/builder/om/php5/PHP5PeerBuilder.php:1126: throw new PropelException('Cannot insert a value for auto-increment primary key ('.".$this->getColumnConstant($col).".')'); lib/vendor/symfony/lib/plugins/sfPropelPlugin/test/functional/fixtures/lib/model/om/BaseArticlePeer.php:1192: throw new PropelException('Cannot insert a value for auto-increment primary key ('.ArticlePeer::ID.')'); lib/vendor/symfony/lib/plugins/sfPropelPlugin/test/functional/fixtures/lib/model/om/BaseAttachmentPeer.php:749: throw new PropelException('Cannot insert a value for auto-increment primary key ('.AttachmentPeer::ID.')'); lib/vendor/symfony/lib/plugins/sfPropelPlugin/test/functional/fixtures/lib/model/om/BaseAuthorPeer.php:483: throw new PropelException('Cannot insert a value for auto-increment primary key ('.AuthorPeer::ID.')'); lib/vendor/symfony/lib/plugins/sfPropelPlugin/test/functional/fixtures/lib/model/om/BaseBookPeer.php:483: throw new PropelException('Cannot insert a value for auto-increment primary key ('.BookPeer::ID.')'); lib/vendor/symfony/lib/plugins/sfPropelPlugin/test/functional/fixtures/lib/model/om/BaseCategoryPeer.php:483: throw new PropelException('Cannot insert a value for auto-increment primary key ('.CategoryPeer::ID.')'); lib/vendor/symfony/lib/plugins/sfPropelPlugin/test/functional/fixtures/lib/model/om/BaseMoviePeer.php:486: throw new PropelException('Cannot insert a value for auto-increment primary key ('.MoviePeer::ID.')'); lib/vendor/symfony/lib/plugins/sfPropelPlugin/test/functional/fixtures/lib/model/om/BaseProductPeer.php:487: throw new PropelException('Cannot insert a value for auto-increment primary key ('.ProductPeer::ID.')'); [/sourcecode] |
sfPropelPlugin/test
下のファイル群はbuild後に作成されたものだろうと推測可能なので問題のbuildされている部分は
1 |
[sourcecode lang="text"] lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/builder/om/php5/PHP5PeerBuilder.php:1126: throw new PropelException('Cannot insert a value for auto-increment primary key ('.".$this->getColumnConstant($col).".')'); [/sourcecode] |
だと特定できます。('.".$this->getColumnConstant($col).".')
と動的に作成されているのでまず間違いないでしょう。
1 |
[sourcecode lang="text"] vi lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/builder/om/php5/php5peerbuilder.php [/sourcecode] |
php5peerbuilder.php
1 |
[sourcecode lang="text"] foreach ($table->getColumns() as $col) { $cfc = $col->getPhpName(); if ($col->isPrimaryKey() && $col->isAutoIncrement() && $table->getIdMethod() != "none" && !$table->isAllowPkInsert()) { $script .= " if (\$criteria->containsKey(".$this->getColumnConstant($col).") && \$criteria->keyContainsValue(" . $this->getColumnConstant($col) . ") ) { throw new PropelException('Cannot insert a value for auto-increment primary key ('.".$this->getColumnConstant($col).".')'); } if (!$this->getPlatform()->supportsInsertNullPk()) { $script .= " // remove pkey col since this table uses auto-increment and passing a null value for it is not valid \$criteria->remove(".$this->getColumnConstant($col)."); } } elseif ($col->isPrimaryKey() && $col->isAutoIncrement() && $table->getIdMethod() != "none" && $table->isAllowPkInsert() && !$this->getPlatform()->supportsInsertNullPk()) { $script .= " // remove pkey col if it is null since this table does not accept that if (\$criteria->containsKey(".$this->getColumnConstant($col).") && !\$criteria->keyContainsValue(" . $this->getColumnConstant($col) . ") ) { \$criteria->remove(".$this->getColumnConstant($col)."); } } } [/sourcecode] |
っと、対象の生成部分のコードがこれですね。 if ($col->isPrimaryKey() && $col->isAutoIncrement() && $table->getIdMethod() != "none" && !$table->isAllowPkInsert()) {
の部分がありますね。このif文に入らないように、false
になるように値を設定してやれば良さそうです。 名前的に$table->isAllowPkInsert()
ってのが怪しいですね。 なのでまたgrep
1 |
[sourcecode lang="text"] $ grep -r isAllowPkInsert lib/vendor lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/builder/om/php5/PHP5PeerBuilder.php:1123: if ($col->isPrimaryKey() && $col->isAutoIncrement() && $table->getIdMethod() != "none" && !$table->isAllowPkInsert()) { lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/builder/om/php5/PHP5PeerBuilder.php:1136: } elseif ($col->isPrimaryKey() && $col->isAutoIncrement() && $table->getIdMethod() != "none" && $table->isAllowPkInsert() && !$this->getPlatform()->supportsInsertNullPk()) { lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/database/model/Table.php:892: public function isAllowPkInsert() [/sourcecode] |
宣言している場所はここですね lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/database/model/Table.php:892: public function isAllowPkInsert()
1 |
[sourcecode lang="text"] vi lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel-generator/classes/propel/engine/database/model/Table.php [/sourcecode] |
Table.php
1 |
[sourcecode lang="text"] /** * Whether we allow to insert primary keys on tables with * idMethod=native * * @return boolean */ public function isAllowPkInsert() { return $this->allowPkInsert; } [/sourcecode] |
のメソッドがそうですね。コメントの内容的にここにtrue
を入れれば目的の「primary keyを指定して登録」が出来るようになるなー $this->allowPkInsert
をセットしてるとこを探しましょう
Table.php
1 |
[sourcecode lang="text"] /** * Sets up the Rule object based on the attributes that were passed to loadFromXML(). * @see parent::loadFromXML() */ public function setupObject() { $this->name = $this->getAttribute("name"); $this->phpName = $this->getAttribute("phpName"); $this->idMethod = $this->getAttribute("idMethod", $this->getDatabase()->getDefaultIdMethod()); $this->allowPkInsert = $this->booleanValue($this->getAttribute("allowPkInsert")); [/sourcecode] |
同じファイル内のsetupObject
で入れているようですね$this->getAttribute("allowPkInsert");
をtrue
に入れれば良い事が解ります。 このgetしているattributeはどこじゃーいとなると、またコメントを見てみると * @see parent::loadFromXML()
の記述が。 XMLを読み込みだったり一緒にname
だったりphpName
だったりidMethod
なりを取得してるーってことでとりあえず当たりが付いたので探索は終了。 XMLを使ったりphpName
だったりの記述があるところはconfig/schema.yml
の中って事が解りました。 試しに変更してみます
1 |
[sourcecode lang="text"] $ vi config/schema.yml $ svn diff hugehu_ge_table: - _attributes: { phpName: HugehuGeTable } + _attributes: { allowPkInsert: true, phpName: HugehuGeTable } $ ./symfony propel:build-model $ svn diff - if ($criteria->containsKey(HugehuGeTablePeer::ID) && $criteria->keyContainsValue(HugehuGeTablePeer::ID) ) { - throw new PropelException('Cannot insert a value for auto-increment primary key ('.HugehuGeTablePeer::ID.')'); - } - [/sourcecode] |
対象の箇所が無事削除されました。 これでおーしまい! の人もいるとは思うのですが、いやいやまだまだ。 弊社の環境ではschema.yml
の直変更はしていなく、build用のデータベースを作成してschema.yml
を自動生成しています。 以下のタスクですね。
1 |
[sourcecode lang="text"] ./symfony propel:build-schema --env="buildonly" [/sourcecode] |
なのでー、書き換えちゃうと次回のschema.yml
作成の時に戻ってしまうんですね。 どうしよー yml作成のタスクでそんなオプション無いか?と思い探してみた物の、無いようですね.. しょうがないので今までのタスクをコピーしてsncPropel:build-schema
を作成、タスクが動くようにclass名を変えたり読み込んでるbaseファイルのパス変えるぐらい。 そして、以下の箇所を
1 |
[sourcecode lang="text"] // Fix database name if (file_exists($xmlSchemaPath)) { $schema = file_get_contents($xmlSchemaPath); $schema = preg_replace('/<database\s+name="[^"]+"/s', '<database name="'.$connection.'" package="lib.model"', $schema); file_put_contents($xmlSchemaPath, $schema); } [/sourcecode] |
こういう風に変更してしまえば
1 |
[sourcecode lang="text"] // Fix database name if (file_exists($xmlSchemaPath)) { $schema = file_get_contents($xmlSchemaPath); $schema = preg_replace('/<database\s+name="[^"]+"/s', '<database name="'.$connection.'" package="lib.model"', $schema); // snc追加分、allowPkInsertでID指定の登録が可能に $schema = preg_replace('/\s+<table\s+(name="[^"]+")/s', '<table ${1} allowPkInsert="true" ', $schema); file_put_contents($xmlSchemaPath, $schema); } [/sourcecode] |
タスク実行でもschema.yml
は書き変わったものになりますね!