商品の販売スケジュールを管理するテーブル設計パターン

商品とその販売スケジュールを管理するためのテーブル設計をするとき、販売スケジュールの情報を別テーブルに分離しておくと再利用性の高い作りに出来る場合があります。一つの設計パターンとしてメモしておきます。

例えば、以下の様な販売方法に対応したいとします。

  • 同時に複数の商品を販売する
  • 期間ごとに販売する商品の組み合わせ(商品セット)が異なる
  • 一度販売した商品の組み合わせは以降も使いまわす場合がある

つまりこういったケースです。

  • 1月に販売する商品
    • 商品A
    • 商品B
  • 2月に販売する商品
    • 商品C
  • 3月に販売する商品(1月の商品セットの使い回し)
    • 商品A
    • 商品B

テーブル設計例

products テーブル(商品)

  • id INT AUTO_INCREMENT
  • name VARCHAR COMMENT ‘商品名’

product_sets テーブル(商品セット)

  • id INT AUTO_INCREMENT
  • name VARCHAR COMMENT ‘商品セット名’
  • is_default BOOL COMMENT ‘基本商品セットフラグ’

product_set_relations テーブル(商品セットに属する商品)

  • id INT AUTO_INCREMENT
  • product_set_id INT
  • product_id INT

product_set_sale_schedules テーブル(商品セットの販売スケジュール)

  • id INT AUTO_INCREMENT
  • product_set_id INT
  • start_datetime DATETIME COMMENT ‘販売開始日時’
  • end_datetime DATETIME COMMENT ‘販売終了日時’

上記スキーマの解説

まず products テーブルに商品情報を持ちます。商品の種類の数だけレコードが出来ます。

そして複数の商品をまとめた商品セットを product_sets としてまとめ、その商品セットの販売期間の情報を product_set_sale_schedules に持つという構成です。

product_set_relations テーブルは多:多のリレーションを解消するための中間テーブルです。商品セットに属する商品一覧の情報を持ちます。

一度販売した商品セットは今後のスケジューリングでも使い回すことが出来ます。

もし期間に該当する商品セットがあった場合、それが優先して有効な商品セットとなりますが、該当するスケジュール情報が無かった場合は is_default が立っている基本商品セットが有効になります。こうしておくことで該当する販売スケジュールのデータが無かった場合のフォールバックが可能となり、常に何かを販売し続けることができます。(その必要が無ければ不要)

また、過去の販売スケジュールの履歴はスケジュールテーブルに蓄積して残すことが出来るため、後から過去の販売スケジュールを確認することができます。

今回の設計パターンについて補足

今回は少し要件が特殊だと思うので、実際には要件に合わせて構成を見なおした方がいいと思います。(設計パターンとか書いておいてすみません。)

例えば今回用いた「商品セット」という概念は、商品の組み合わせの再利用をしやすくする一方で、細やかな組み合わせの変更を難しくしています。

再利用されるケースが少ない場合は商品セットテーブルを無くしてしまい、商品ごとにスケジュールを設定する構成にしたほうが柔軟に対応できるでしょう。

「運用でカバー」は悲しい

運用でカバーという名のもと無駄なデータが生まれていくのは設計者として悲しいことです。

要件をよく確認して適切にテーブル設計をしていきたいものです。

CakePHP3のBakeコマンドで生成されるファイルをカスタマイズする

TL;DR

CakePHP3 の bake コマンドで生成されるファイルの内容をカスタマイズしたい場合、対応するテンプレートファイルを src 以下にコピーして追加して内容を書き換えるだけでカスタマイズできます。

Model ファイルを bake する例

まずは通常通り Model 関連ファイルを作成するコマンドです。
テーブル名が users の場合は以下のようなコマンドを実行すると焼き上がります。

bin/cake bake model Users

実行すると以下4ファイルが生成されます。

  • src/Model/Table/UsersTable.php (Table)
  • src/Model/Entity/User.php (Entity)
  • tests/Fixture/UsersFixture.php (Fixture)
  • tests/TestCase/Model/Table/UsersTableTest.php (TestCase)

生成されるファイルの内容を一部カスタマイズする方法

上記のコマンドで生成されるファイルのコード内容を一部書き換えたい場合どうすればいいでしょうか?
例えば Table クラスファイルの内容を書き換えたい場合、以下の手順で出来ます。

  1. vendor/cakephp/bake/src/Template/Bake/Model/table.ctp にあるファイルをコピーする(通常はこのファイルを元に生成が行われる)
  2. コピーしたファイルを src/Template/Bake/Model/table.ctp に貼り付ける
  3. 貼り付けたファイルの内容の書き換えたい箇所を書き換える

この状態で bake コマンドを実行するとコピーした ctp ファイルを元にクラスファイルの生成が行われます。
Entity クラスや Controller クラスなどを書き換えたい場合も同様の方法で可能です。

ctp ファイルの書き方やその他詳しい情報は CakePHP 公式ドキュメントの Extending Bake — CakePHP Cookbook 3.x documentation を参照してください。

更にカスタマイズをしたい場合

更なるカスタマイズをしたい場合、ここでは詳しく説明しませんが bake 用の Theme Plugin を作成してそれを bake 時に有効にして適用する方法や、新しい bake コマンドオプションを作ってしまう方法などがあります。

例えば Bake\Shell\Task\SimpleBakeTask クラスを継承して FooBakeTask クラスを作成することで、以下のようなコマンドを実行できるようになります。

bin/cake bake foo

やろうと思えば専用のコマンドラインオプションの追加実装なども出来るため、かなり自由度の高いカスタマイズが出来そうです。

ClipMenuがCPU使用率100%以上になる場合

ClipMenu を使用していると稀に高負荷状態に陥り CPU 使用率が100%を上回る状態が続くことがあります。CPU ファンがうなり続ける状態です。

大抵の場合はアプリを強制終了し再起動すれば治るのですが、アプリを何度再起動しても高負荷状態が続く現象に遭遇しました。

これはクリップボード履歴データが怪しいということで、初期化を行ったところ解決しましたので方法をメモしておきます。

クリップボードデータの初期化方法

ClipMenu は操作不能のため、アプリを終了した状態でターミナル上で以下の操作を行い初期化しました。

cd ~/Library/Application\ Support/ClipMenu/
cp clips.data clips.data.bak
:> clips.data

最後のコロンコマンドの意味は シェルスクリプトでset -eしているときに処理を中断せずエラーを扱う方法 で少し触れています。何もしないコマンドですが、リダイレクトするとそのファイルが空になります。

~/Library/Application Support/ClipMenu/clips.data がクリップボードデータの実体ファイルパスになります。履歴データは消えてしまいますが、同様の症状の場合は試してみるといいかも。