====== Apricot データベースとモデル ====== --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-05-06// [[apricot:top|Apricot に戻る]] 関連記事 * [[apricot:configuration|Apricot プロジェクトの作成]] * [[apricot:public|Apricot 公開フォルダ]] * [[apricot:core:top|Apricot コア]] * Apricot アプリ * [[apricot:app:top|Apricot アプリ作成の準備]] * [[apricot:app:home|Apricot ホーム画面]] * [[apricot:app:error|Apricot エラー画面]] * Apricot データベースとモデル * [[apricot:app:user-list|Apricot ユーザ一覧画面]] * [[apricot:app:user-edit|Apricot ユーザ登録画面]] * [[apricot:app:validation|Apricot バリデーション]] * [[apricot:app:transaction|Apricot トランザクション]] * [[apricot:ext:middleware|Apricot 拡張]] \\ 本章ではデータベースの設定とモデルの実装を行います。 ---- ===== データベースの構築 ===== Apricotでは以下のユーザテーブルを持っています。 テーブル名 : **user** ^カラム名^型^主Key^属性^説明^ |id|integer|●|autoincrement|ID| |account|text| |unique not null|アカウント| |password|text| |not null|パスワード| |email|text| |not null|Eメールアドレス| |note|text| | |備考| |remember_token|text| | |自動ログイン用| |created_at|text| |not null|作成日| |updated_at|text| |not null|更新日| |version_no|integer| |default 0 not null|バージョンNo| * ユーザ認証は(account,password)で行います * remember_tokenは自動ログイン用の認証トークンです * version_noは楽観的ロックで使用します ==== SQLファイル ==== Apricotではアプリ起動時にデータベースにテーブルが存在しない時、テーブルを自動生成します。テーブル作成用のsqlは以下のファイルに保存します。このファイルは後述のORMの[[#初期設定ファイル|初期設定ファイル]]で使用されます。 {{fa>folder-open-o}} ** /apricot/assets/sql ** /* * User Table */ CREATE TABLE IF NOT EXISTS user ( id integer primary key autoincrement, account text unique not null, password text, email text not null, note text, remember_token text, created_at text not null, updated_at text not null, version_no integer default 0 not null ); === SQLファイルについて === * SQLの文法及び使用できる関数などは使用しているデーターベース(Apricotでは''SQLite'')に依存します。 * コメントは行コメント( ''--Comment'' )とブロックコメント( ''/* Comment */'' )が使用できます。 * 文はセミコロン( '';'' )で区切って下さい。 * 連続する空白( ''TAB'', ''Space'', ''改行文字'' )は1つの空白と同じにみなされます。 \\ ===== ORMの設定 ===== ORマッパーには[[basic-library:idiorm:1.5|Idiorm]]を使用します。Idiormは元々シングルトンとして実装してあるのでそのまま使えます。使い方やメソッドについてはIdiormの[[https://idiorm.readthedocs.io/en/latest/|マニュアル]]を参照して下さい。 ==== 設定ファイル ==== {{fa>folder-open-o}} ** /apricot/config/setting ** [ 'db_file' => var_dir('db/apricot.sqlite'), 'connection_string' => 'sqlite:'.var_dir('db/apricot.sqlite'), 'caching' => true, 'logging' => true, ], 'initial_data' => [ 'user'=> [ 'exec' =>[ 'delete from sqlite_sequence where name=\'user\'', ], 'rows' => [ [ 'account' =>'root', 'password' =>password_hash('', PASSWORD_DEFAULT), 'email' =>'root@sample.com', 'note' =>'Initial User', ], ], ], ], ]; * sqlite : 接続設定(apricotではSQLiteを使用します) * sqlite.db_file --- データベースファイルのパス (既定値は var/db/apricot.sqlite') * sqlite.connection_string --- 接続文字列 * sqlite.caching --- キャッシングの有無 * sqlite.logging --- ロギングの有無 * initial_data : 初期データ (テーブルが空の時に自動設定されます) * {テーブル名} --- ここではユーザテーブル( user )を指定しています * exec --- 実行したいSQLを記述します(複数指定可) * rows --- 行データを設定します(複数指定可) 接続設定の詳細は以下を参照して下さい:\\ https://idiorm.readthedocs.io/en/latest/configuration.html#id1 \\ ==== 初期設定ファイル ==== データベースの設定は初期設定ファイルで行います。 {{fa>folder-open-o}} ** /apricot/config/setup ** config('idiorm.sqlite.connection_string'), 'caching' => config('idiorm.sqlite.caching',false), 'logging' => false, 'logger' => function($log_string, $query_time) { // SQL debug logging \Core\Log::info("SQL",[$log_string]); }, ]); //------------------------------------------- // テーブルの作成 (新しくDBを作った時) //------------------------------------------- if ($new_db_file) { $sql_text = file_get_sql(assets_dir('sql/create.sql')); if (!empty($sql_text)) { foreach($sql_text as $sql) { ORM::get_db()->exec($sql); } } } //------------------------------------------- // 初期ユーザの作成 (ユーザテーブルが空の時) //------------------------------------------- $initial_data = config('idiorm.initial_data'); if (isset($initial_data)) { foreach($initial_data as $key=>$item) { if(ORM::for_table($key)->find_one()===false) { if (array_key_exists('exec', $item)) { // SQLの実行 $exec = (array)$item['exec']; foreach($exec as $sql) { ORM::get_db()->exec($sql); } } if (array_key_exists('rows', $item)) { // 新しいレコードの作成 $rows = (array)$item['rows']; foreach($rows as $row) { $row = ORM::for_table($key)->create($row); $row->set_expr('created_at', "datetime('now')"); $row->set_expr('updated_at', "datetime('now')"); $row->save(); } } } } } // SQLログ開始 ORM::configure('logging' , config('idiorm.sqlite.logging',false)); return true; // Must return true on success }; 初期設定ファイルでは以下の事をおこないます: * データベースの保存フォルダが存在しない場合は作成します * データベースへの接続 * テーブルの作成 (新しくDBを作った時) * 初期ユーザの作成 (ユーザテーブルが空の時) * SQLログの開始 (logging設定がtrueの場合) \\ ===== アプリケーション設定の変更 ===== 上で作った idiorm.setup.php をアプリケーションの設定ファイル(app.php)に追加します。 {{fa>folder-open-o}} ** /apricot/config** [ config_dir('setup/whoops.setup.php'), /* Error handler(whoops) */ config_dir('setup/bladeone.setup.php'), /* View template (BladeOne) */ config_dir('setup/aliases.setup.php'), /* Class aliases for view template and so on */ config_dir('setup/idiorm.setup.php'), /* ORM(idiorm) */ ], 'middleware' =>[], 'auth' =>[], 'csrf' =>[], ]; \\ ===== テスト実行 ===== データベースファイル(apricot.sqlite)を作ってみましょう。ブラウザ上で以下のURLにアクセス、Apricotのホーム画面を表示して下さい。 http://localhost/ws2019/apricot/public/ まだユーザ登録機能を実装していないので、アプリからユーザテーブルの内容を見る事はできませんが、以下の場所にデータベースファイルが作成されていることを確認して下さい。 {{fa>file-o}} ** /apricot/var/db/apricot.sqlite ** Sqliteデータベースの内容を確認するには、[[tools:a5m2|A5:SQL Mk-2]] などのデータベースクライアントを使用して下さい。また、Eclipseからデータベースの内容を参照したい場合は、DBeaverプラグイン が利用できます。 === DBeaverプラグインのインストール方法 === * メニューから[ヘルプ][新規ソフトウェアのインストール...]を選択 * [--すべての使用可能なサイト--]を選択 * [一般用ツール][マーケットプレース・クライアント]をインストール * Eclipseの再起動 * メニューから[ヘルプ][Eclipseマーケットプレース]を選択 * [DBeaver]を検索してインストール * Eclipseの再起動 === DBeaverプラグインの使用方法 === * メニューから[データベース][新しい接続]を選択して ''apricot.sqlite'' に接続 * [ウィンドウ][ビューの表示][その他...]を選択 * [データベース][データベースブラウザ]を選択 * データベースブラウザから * SQLite - apricot.sqlite をクリック * SQLiteドライバー(jdbc)をダウンロード(初回のみ) * [テーブル][user]をクリック [{{:apricot:app:app04.png?nolink|}}] \\ ===== モデルクラス ===== ORマッパーが使えるようになったので、モデルのベースクラス( Model )を作ります。Modelクラスは必ず継承して使い、以下のメソッドを持ちます。詳しくはソースコードを参照して下さい。 ^メソッド名^機能^ |tableName()\\ :string|テーブル名の取得\\ テーブル名(snake_case)はクラス名(UpperCamelCase)から自動判定します。| |for_table()\\ :ORM|ORMオブジェクトの取得| |findAll()\\ :array|全件検索\\ ORMの配列を返します。| |findOne\\ (int $id):mixed|主キー検索\\ 見つかった場合は ORM を、それ以外は false を返します。| |create\\ (array $inputs=null):ORM|モデルの新規作成| |insert\\ (array $inputs):ORM|レコードの挿入| |update\\ ($id, array $inputs):ORM|レコードの更新\\ レコードが存在しない時、ApplicationExceptionが発生します。\\ 楽観的ロック例外を検知した時、OptimissticLockExceptionが発生します。| |delete\\ ($id):ORM|レコードの削除\\ レコードが存在しない時、ApplicationExceptionが発生します。| |isSuccess()\\ :bool|最新の更新結果を取得します(insert/update/delete)| ソースコードを以下に示します。 {{fa>folder-open-o}} ** /apricot/app/Foundation ** for_table()->find_many(); } /** * 主キー検索 * @param int $id * @return \ORM|false returna single instance of the ORM class, or false if norows were returned. */ public function findOne(int $id) { return $this->for_table()->find_one($id); } /** * 新規作成 * @return \ORM */ public function create(array $inputs=null):ORM { return $this->for_table()->create($inputs); } /** * 新規保存 * @param array $inputs * @return \ORM */ public function insert(array $inputs):ORM { $row = $this->for_table()->create($inputs); $row->set_expr('created_at', "datetime('now','localtime')"); $row->set_expr('updated_at', "datetime('now','localtime')"); $this->success = $row->save(); return $row; } /** * データ更新 * @param mixed $id * @param array $inputs * @return \ORM */ public function update($id, array $inputs):ORM { // ApricotではSQLite3.0.8以上の使用を前提としており、トランザクション分離レベルはデフォルト値がDEFERREDです。 // DEFERRED は最初の読み取り時に共有ロックが掛かります(SQLiteのロックはデータベースロックです)。 // 従って、version_no読み取り後はトランザクション終了まで他の更新は発生しません。 // NOTE: 他のデータベースの場合は、ここで行ロックを取得してレコードの検索を行います(select for update) $row = $this->for_table()->find_one($id); if ($row===false) { throw new ApplicationException(__('messages.error.db.update')); } // 楽観的ロックの検証 if ($row->version_no != $inputs['version_no']) { throw new OptimissticLockException(); } // データ更新 $row->set($inputs); $row->set_expr('updated_at', "datetime('now','localtime')"); $row->set_expr('version_no', "version_no+1"); $this->success = $row->save(); return $row; } /** * データ削除 * @param mixed $id * @return \ORM */ public function delete($id):ORM { $row = $this->for_table()->find_one($id); if ($row===false) { throw new ApplicationException(__('messages.error.db.delete')); } $this->success = $row->delete(); return $row; } /** * 最新の更新結果の取得(insert/update/delete) * @return bool */ public function isSuccess():bool { return $this->success; } } ORMオブジェクトについては、以下のIdiormのドキュメントを参照して下さい: * https://idiorm.readthedocs.io/en/latest/ \\