データのDB保存(オリジナル版) | save | saveAll
テストコード
$data = [
['id'=>'1', 'kani_val'=>'1', 'kani_name'=>'大猫', 'kani_date'=>'2014-04-01', 'kani_group'=>'2', 'kani_dt'=>'2014-12-12 00:00:00', 'note'=>'', 'delete_flg'=>'0', 'update_user'=>'test', 'ip_addr'=>'::1', 'created'=>'2012-12-12'],
['id'=>2, 'kani_val'=>'99', 'kani_name'=>'kani', 'kani_date'=>'', 'dummy'=>'エラー'],
['id'=>'4', 'kani_val'=>'4', 'kani_name'=>'buta', 'kani_date'=>'2014-04-04', 'kani_group'=>'2', 'kani_dt'=>'2014-12-12 00:00:03', 'note'=>'AA¥r¥nBBB¥r¥n<input />', 'delete_flg'=>'0', 'update_user'=>'kani'],
['id'=>'', 'kani_val'=>'3', 'kani_name'=>'ニューレコード', 'kani_date'=>'2015-09-17', 'kani_group'=>'2', 'kani_dt'=>'2014-12-12 00:00:02', 'note'=>'', 'delete_flg'=>'0', 'update_user'=>'kani', 'ip_addr'=>'::1', 'created'=>'2012-12-13', 'modified'=>'2012-12-14'],
];
require_once 'SaveData.php';
$saveData = new SaveData();
// データDB登録テスト
$res = $saveData->saveAll('kanis', $data);
debug($res['rData']);
debug($res['err_msg']);
// エンティティDB登録テスト
$ent = $data[3];
$res = $saveData->save('kanis', $ent);
debug($res['rEnt']);
debug($res['err_msg']);
// 行削除テスト
$saveData->delete('kanis', 20);
function debug($value){
echo '<pre>';
var_dump($value);
echo '</pre>';
}
<?php
/**
* データ保存クラス
* @version 2.0.0
* @date 2019-11-6 | 2020-3-31
* @author kenji uehara
* @since 2020-3-31
* @license MIT
*
*/
class SaveData{
private $dao;
private $fieldTypeHm; // フィールド型ハッシュマップ
/**
* コンストラクタ
* @param IDao $dao
*/
public function __construct(IDao $dao = null){
if($dao == null){
require_once 'PdoDao.php';
$dao = new PdoDao();
}
$this->dao = $dao;
}
/**
* DAOを取得する
* @return IDao $dao
*/
public function getDao(){
return $this->dao;
}
/**
* データ保存する
*
* @note
* idフィールドが主キー、オートインクリメントであるテーブルが対象。
* 1行でもDB登録に失敗すると一旦すべてロールバックする。
* トランザクション制御が内部でなされている。idをレスポンスとして取得する仕様上、この制御ははずせず。
*
* @param string $tbl_name テーブル名
* @param array $data データ(エンティティ配列型)
* @return array
* - rData 処理結果データ
* - err_msg エラーメッセージ
*/
public function saveAll($tbl_name, &$data){
if(empty($data)) return 1;
$sqls = $this->makeInsertSqlDup($tbl_name, $data);
$rData = []; // 処理結果データ
$this->begin();
foreach($sqls as $i => $sql){
$err_msg = $this->dao->query($sql);
if(!empty($err_msg)) break;
$id = $this->getValue('SELECT LAST_INSERT_ID()');
$rEnt = [];
if($id == 0){
$rEnt['id'] = $data[$i]['id'];
$rEnt['exe_type'] = 'update';
}else{
$rEnt['id'] = $id;
$rEnt['exe_type'] = 'insert';
}
$rData[] = $rEnt;
}
if(empty($err_msg)){
$this->commit();
}else{
$this->rollback();
}
$res = ['rData'=>$rData, 'err_msg'=>$err_msg];
return $res;
}
/**
* データ保存する
*
* @note
* idフィールドが主キー、オートインクリメントであるテーブルが対象。
* トランザクション制御が内部でなされている。idをレスポンスとして取得する仕様上、この制御ははずせず。
*
* @param string $tbl_name テーブル名
* @param array $ent エンティティ
* @return array
* - rEnt 処理結果エンティティ
* - err_msg エラーメッセージ
*
*/
public function save($tbl_name, &$ent){
if(empty($ent)) return 1;
$sql = $this->makeInsertSqlDupFromEnt($tbl_name, $ent);
$this->begin();
$err_msg = $this->dao->query($sql);
$rEnt = [];
if(empty($err_msg)){
$id = $this->getValue('SELECT LAST_INSERT_ID()');
$this->commit();
if($id == 0){
$rEnt['id'] = $ent['id'];
$rEnt['exe_type'] = 'update';
}else{
$rEnt['id'] = $id;
$rEnt['exe_type'] = 'insert';
}
}else{
$this->rollback();
}
$res = ['rEnt'=>$rEnt, 'err_msg'=>$err_msg];
return $res;
}
/**
* テーブルに存在しないフィールドを除去するフィルター
* @param string $tbl_name テーブル名
* @param array $data
* @return array フィルター後のデータ
*/
public function filterData($tbl_name, &$data){
// 列情報を取得する
$fields = $this->getFields($tbl_name);
$data2 = [];
foreach($data as $ent){
$ent2 = [];
foreach($ent as $field=>$value){
if(in_array($field, $fields)){
$ent2[$field] = $value;
}
}
$data2[] = $ent2;
}
return $data2;
}
/**
* フィールドリストを取得する
* @param string $tbl_name テーブル名
* @return array フィールドリスト
*/
public function getFields($tbl_name){
$columns = $this->getColumns($tbl_name);
$fields = [];
foreach($columns as $clmEnt){
$fields[] = $clmEnt['Field'];
}
return $fields;
}
/**
* 列情報を取得する
* @param string $tbl_name テーブル名
* @return array $columns 列情報
*/
public function getColumns($tbl_name){
$columns = $this->dao->getData("SHOW FULL COLUMNS FROM {$tbl_name}");
return $columns;
}
/**
* エンティティからDUPLICATE型のINSERT文を作成する(重複は更新)
*
* @note
* テーブルに存在しないフィールドは無視する。
*
* @param string $tbl_name テーブル名
* @param array $ent エンティティ
* @return array INSERT SQLリスト
*
*/
public function makeInsertSqlDupFromEnt($tbl_name, $ent){
if(empty($ent)) return [];
// フィールド型ハッシュマップを取得する
$fieldTypeHm = $this->getFieldTypeHashmap($tbl_name);
$fields_str = '';
$i_vals_str = '';
$u_vals_str = '';
foreach($ent as $field => $value){
if(empty($fieldTypeHm[$field])) continue;
if($field == 'id' && empty($value)) continue;
$fields_str .= $field . ',';
$type = $fieldTypeHm[$field];
// 日付系に空をセットする場合はNULLをセット
if(empty($value) && ($type == 'date' || $type == 'datetime')){
$i_vals_str .= "NULL,";
$u_vals_str .= "{$field}=NULL,";
}else{
$i_vals_str .= "'{$value}',";
$u_vals_str .= "{$field}='{$value}',";
}
}
// 末尾の一文字であるコンマを削る
$i_vals_str = mb_substr($i_vals_str,0,mb_strlen($i_vals_str)-1);
$u_vals_str = mb_substr($u_vals_str,0,mb_strlen($u_vals_str)-1);
$fields_str = mb_substr($fields_str,0,mb_strlen($fields_str)-1);
$sql = "
INSERT INTO {$tbl_name} ({$fields_str})
VALUES ({$i_vals_str})
ON DUPLICATE KEY UPDATE {$u_vals_str}
";
return $sql;
}
/**
* フィールド型ハッシュマップを取得する
* @param string $tbl_name
* @return array フィールド型ハッシュマップ
*/
private function getFieldTypeHashmap($tbl_name){
if($this->fieldTypeHm) return $this->fieldTypeHm;
$columns = $this->getColumns($tbl_name); // 列情報を取得する
$fieldTypeHm = [];
foreach($columns as $clmEnt){
$field = $clmEnt['Field'];
$type = $clmEnt['Type'];
$fieldTypeHm[$field] = $type;
}
$this->fieldTypeHm = $fieldTypeHm;
return $fieldTypeHm;
}
/**
* データからDUPLICATE型のINSERT文を作成する
* @param string $tbl_name テーブル名
* @param array $data エンティティ配列型のデータ
* @return array INSERT SQLリスト
*
*/
public function makeInsertSqlDup($tbl_name, &$data){
if(empty($data)) return [];
$sqls = [];
foreach($data as $ent){
$sqls[] = $this->makeInsertSqlDupFromEnt($tbl_name, $ent);
}
return $sqls;
return $sqls;
}
/**
* データからINSERTとUPDATEのSQL文を生成する
* @param string $tbl_name テーブル名
* @param array $data エンティティ配列型のデータ
* @param string $id_filed IDフィールド(主キーフィールド)
* @return array|string[][]
*/
public function createInsertAndUpdate($tbl_name, $data, $id_filed='id'){
if(empty($data)) return array();
// 列名群文字列を組み立て
$ent0 = current($data);
$keys = array_keys($ent0);
$clms_str = implode(',', $keys); // 列名群文字列
$inserts = array(); // INSERT SQLリスト
$updates = array(); // UPDATE SQLリスト
foreach($data as &$ent){
// IDが空ならINSERT文を組み立て
if(empty($ent[$id_filed])){
$inserts[] = $this->makeInsertSql($tbl_name, $ent); // INSERT文を作成する
}
// IDが存在すればUPDATE文を組み立て
else{
$updates[] = $this->makeUpdateSql($tbl_name, $ent); // UPDATE文を作成する
}
}
unset($ent);
$res = [
'inserts' => $inserts,
'updates' => $updates,
];
return $res;
}
/**
* INSERT文を作成する
* @param string $tbl_name テーブル名
* @param array $ent 登録データのエンティティ
* @return string INSERT文
*/
public function makeInsertSql($tbl_name, &$ent){
$clms_str = '';
$vals_str = '';
foreach($ent as $field => $value){
$clms_str .= $field . ',';
if(empty($value) && $value !==0 && $value !=='0') $value = '';
$vals_str .= "'{$value}',";
}
// 末尾の一文字であるコンマを削る
$clms_str = mb_substr($clms_str,0,mb_strlen($clms_str)-1);
$vals_str = mb_substr($vals_str,0,mb_strlen($vals_str)-1);
$insert_sql = "INSERT INTO {$tbl_name} ({$clms_str}) VALUES ({$vals_str});";
return $insert_sql;
}
/**
* UPDATE文を作成する(非推奨)
* @param string $tbl_name テーブル名
* @param array $ent 登録データのエンティティ
* @param string $id_filed IDフィールド(主キーフィールド)
* @return string UPDATE文
*/
public function makeUpdateSql($tbl_name, &$ent, $id_filed='id'){
if(empty($ent[$id_filed])) throw new Exception("makeUpdateSql: {$id_filed}が空です。");
$vals_str = '';
foreach($ent as $field => $value){
if($value === null) continue;
$vals_str .= "{$field}='{$value}',";
}
$vals_str = mb_substr($vals_str,0,mb_strlen($vals_str)-1);// 末尾の一文字であるコンマを削る
$update_sql = "UPDATE {$tbl_name} SET {$vals_str} WHERE {$id_filed}={$ent['id']}";
return $update_sql;
}
/**
* SQLによるエンティティ配列型のデータ取得
* @param string $sql
*/
public function getData($sql){
return $this->dao->getData($sql);
}
/**
* SQLによるエンティティ配列型のデータ取得(エイリアス)
* @param string $sql
*/
public function getDataBySql($sql){
return $this->getData($sql);
}
/**
* SQLによるエンティティ取得
* @param string $sql
*/
public function getEnt($sql){
$data = $this->dao->getData($sql);
if(empty($data)) return [];
return $data[0];
}
/**
* SQLによるエンティティ取得(エイリアス)
* @param string $sql
*/
public function getEntBySql($sql){
return $this->getEnt($sql);
}
/**
* 一つの値を取得する
* @param string $sql
* @return NULL|mixed
*/
public function getValue($sql){
$ent = $this->getEnt($sql);
if(empty($ent)) return null;
$value = current($ent);;
return $value;
}
/**
* エンティティをDBにINSERT
* @param string $tbl_name テーブル名
* @param array $ent エンティティ
* @return string $err_msg;
*/
public function insertEntity($tbl_name, $ent){
$sql = $this->makeInsertSql($tbl_name, $ent);
return $this->dao->query($sql);
}
/**
* オートインクリメントのリセット
* @param string $tbl_name テーブル名
* @param number $reset_value リセット値
* @return string $err_msg;
*/
public function resetAutoIncrement($tbl_name, $reset_value=1){
$sql = "ALTER TABLE {$tbl_name} auto_increment = {$reset_value};";
return $this->dao->query($sql);
}
/**
* 削除
* @param string $tbl_name テーブル名
* @param int $id
* @return string $err_msg;
*/
public function delete($tbl_name, $id){
$sql = "DELETE FROM {$tbl_name} WHERE id={$id}";
return $this->dao->query($sql);
}
/**
* SQLを実行
* @param string $sql
* @return string $err_msg;
*/
public function query($sql){
return $this->dao->query($sql);
}
/**
* SQLインジェクションサニタイズ(配列用)
*
* @note
* SQLインジェクション対策のためデータをサニタイズする。
* 高速化のため、引数は参照(ポインタ)にしている。
*
* @param array サニタイズデコード対象のデータ
* @return void
*/
public function sql_sanitize(&$data){
if(is_array($data)){
foreach($data as &$val){
$this->sql_sanitize($val);
}
unset($val);
}elseif(gettype($data)=='string'){
$data = addslashes($data);// SQLインジェクション のサニタイズ
}else{
// 何もしない
}
}
public function begin(){
$this->dao->begin();
}
public function rollback(){
$this->dao->rollback();
}
public function commit(){
$this->dao->commit();
}
}
interface IDao{
public function sqlExe($sql);
public function begin();
public function rollback();
public function commit();
}
<?php
require_once 'IDao.php';
require_once 'CrudBaseConfig.php';
/**
* PDOのDAO(データベースアクセスオブジェクト)
*
* @date 2019-10-26 | 2020-4-1
* @version 1.1.0
* @license MIT
* @author Kenji Uehara
*
*/
class PdoDao implements IDao
{
var $dao;
/**
* DAO(データベースアクセスオブジェクト)を取得する
* @return object Dao
*/
public function getDao(){
if($this->dao) return $this->dao;
require_once 'CrudBaseConfig.php';
$cbf = new CrudBaseConfig();
$dbConf = $cbf->getDbConfig();
try {
$dao = new PDO("mysql:host={$dbConf['host']};dbname={$dbConf['db_name']};charset=utf8",$dbConf['user'],$dbConf['pw'],
array(PDO::ATTR_EMULATE_PREPARES => false));
} catch (PDOException $e) {
exit('データベース接続失敗。'.$e->getMessage());
die;
}
$this->dao = $dao;
return $dao;
}
/**
* SQLを実行してデータを取得する
* @return boolean|PDOStatement[][]
*/
public function getData($sql){
$dao = $this->getDao();
$stmt = $dao->query($sql);
if($stmt === false) {
var_dump('SQLエラー→' . $sql);
return false;
}
$data = [];
foreach ($stmt as $row) {
$ent = [];
foreach($row as $key => $value){
if(!is_numeric($key)){
$ent[$key] = $value;
}
}
$data[] = $ent;
}
return $data;
}
/**
* 単純なクエリー実行(SELECT用ではない)
* @param string $sql
* {@inheritDoc}
* @see IDao::sqlExe()
*/
public function sqlExe($sql){
return $this->dao->query($sql);
}
/**
* 単純なクエリー実行(SELECT用ではない)
* @param string $sql
* @return string エラーメッセージ
*/
public function query($sql){
$err_msg = '';
$res = $this->dao->query($sql);
if($res === false){
$errInfo = $this->dao->errorInfo();
$err_msg = "
<pre>
SQLエラー→{$sql}
$errInfo[0]
$errInfo[1]
$errInfo[2]
</pre>
";
var_dump($err_msg);
}
return $err_msg;
}
public function begin(){
$dao = $this->getDao();
$stmt = $dao->query('BEGIN');
}
public function rollback(){
$dao = $this->getDao();
$stmt = $dao->query('ROLLBACK');
}
public function commit(){
$dao = $this->getDao();
$stmt = $dao->query('COMMIT');
}
}
<?php
class CrudBaseConfig{
/**
* DB設定
* @return string[] DB設定情報
*/
public function getDbConfig(){
$dbConfig = [
'host'=>'localhost',
'db_name'=>'cake_demo',
'user'=>'root',
'pw'=>'neko',
];
return $dbConfig;
}
}