ストアドプロシージャを jQuery + PHPで呼び出してみた


#はじめに
・トランザクション処理をストアドプロシージャにまとめることでソースをすっきりさせたい衝動にかられ、ストアドプロシージャを呼び出してみた。
・いままでクライアント側にSQLをベタ打ちしていてセキュリティーの問題がやばいみたいなので...

#開発環境
・サーバー構成
　WindowsServr2012R2
　Apache + SQLServer2012 + PHP
　
・クライアント構成
　JavaScript + jQuery

#実装コード
<b>・クライアントからのAPIの呼び出し</b>


```JS:classDatabase.js
//=========================================
// Database クラス
// データベースに関する命令をまとめる
//=========================================
const DatabaseAsyncOn = true;
const DatabaseAsyncOff = false;

function Database(accountName, userName, async) {
    this.accountName = "dev"; // accountName;
    this.userName = "dev"; // userName;
    this.async = false; // async;
}
Database.prototype = {
    //=========================================
    // SELECT メソッド（prepareを使用したもの）
    // DBからSQLを使ってデータをGETする
    //=========================================
    getProcedures: function(mappingName, prepare) {
        var returnData = null;
        $.ajax({
            type: "POST",
            url: "../../common/php/getProcedures.php",
            data: {
                "accountName": this.accountName,
                "userName": this.userName,
                "mappingName": mappingName,
                "prepare[]": prepare
            },
            dataType: "json",
            async: this.async,
            // 成功したときの処理
            success: function(data) {
                returnData = data;
                console.log('\n\nmappingName: ' + mappingName);
                console.log('parametter: ' + prepare);
                console.log('data: ' + data);
            },
            // エラー処理
            error: function(data) {
                returnData = false;
                console.log('\n\nmappingName: ' + mappingName);
                console.log('parametter: ' + prepare);
                console.log('data: ' + data);
            }
        });
        return returnData;
    }
};
```

```JS:main.js
    // APIの呼び出し
    // マッピング：200402
    self.midokuMember = self.database.getProcedures(
            'mapping200402',[
                self.companyID,
                eventNo,
                today
            ]
    );
```


<b>サーバー側</b>
　ストアドプロシージャ呼び出し用のAPI

```PHP:setProcedures.php
<?php
  $accountName = $_POST["accountName"];
  $userName    = $_POST["userName"];
  $mappingName = $_POST["mappingName"];
  $prepare     = $_POST["prepare"];
  $org_timezone = date_default_timezone_get();
  date_default_timezone_set('Asia/Tokyo');
  $dsn = 'sqlsrv:server=【IPアドレス】;database=' . $accountName;
  $password = '【DB接続パスワード】';
  try {
    $dbh = new PDO($dsn, $userName, $password);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE , PDO::FETCH_ASSOC);
    //////////////////////////////////////////////
    // セット処理
    //////////////////////////////////////////////
    // ステートメント準備
    $stringPrepare = "{CALL ".$mappingName."(";
    foreach ($prepare as $key => $value) {
      # code...
      $stringPrepare = $stringPrepare."?,";
    }
    if (count($prepare) != 0) {
      $stringPrepare = substr($stringPrepare, 0, -1);   // 最後尾の文字を削除する
    }
    $stringPrepare = $stringPrepare.")}";
    error_log($stringPrepare);
    $stmt = $dbh->prepare($stringPrepare);
    for ($i=0; $i < count($prepare); $i++) {
      # code...
      $stmt->bindParam($i+1, $prepare[$i], PDO::PARAM_STR);
    }
    $stmt -> execute();
    echo "\nPDO::errorInfo():\n";
    print_r($dbh->errorInfo());
    $dbh = null;
    date_default_timezone_set($org_timezone);
  }
  catch (PDOException $e) {
    echo 'Error : ',  $e->getMessage(), "\n";
    die('MSSQL Serer Connect Error');
  }
?>
```

<b>呼び出し元のストアドプロシージャ</b>

```SQL:mapping200409.sql
USE [dev]
GO
/****** Object:  StoredProcedure [dbo].[mapping200402]    Script Date: 2018/12/26 12:10:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[mapping200402]
  @companyID nvarchar(4),
  @eventNo int,
  @today nvarchar(10)
AS
BEGIN TRY
    BEGIN TRANSACTION        --トランザクションの開始
        BEGIN
            -- マッピング2：未読者の取得
			SELECT MK_smartPhone.companyID,
				   MK_smartPhone.staffID,
				   MK_staff.name
			FROM   MK_smartPhone INNER JOIN MK_staff ON 
				   MK_smartPhone.companyID = MK_staff.companyID AND 
				   MK_smartPhone.staffID = MK_staff.staffID LEFT OUTER JOIN
				   MK_history ON 
				   MK_smartPhone.companyID = MK_history.companyID AND 
				   MK_smartPhone.staffID = dbo.MK_history.staffID
			WHERE  (MK_smartPhone.name <> N'card') AND 
				   ((MK_history.category = 2) AND 
				   (MK_history.flag = 0) AND 
				   (MK_history.companyID = @companyID) AND 
				   (MK_history.externNo = @eventNo) AND 
				   (MK_staff.retireDate > @today OR
					MK_staff.retireDate IS NULL OR
					MK_staff.retireDate = '') OR
				   (MK_history.flag IS NULL))
			GROUP BY MK_smartPhone.companyID,
					 MK_smartPhone.staffID, 
					 MK_staff.name, 
					 MK_history.externNo
			ORDER BY MK_smartPhone.staffID			
        END
    COMMIT TRANSACTION       --トランザクションを確定
END TRY


--例外処理
BEGIN CATCH
    ROLLBACK TRANSACTION     --トランザクションを取り消し
    PRINT ERROR_MESSAGE()    --エラー内容を戻す
    PRINT 'ROLLBACK TRANSACTION'
END CATCH

RETURN
```

#まとめ
・SQLを別ファイルに分離してソースの読みやすさ管理のしやすさの向上が図れた。
・言語ごとに得意分野があるのでそれぞれ適材適所で使用していきたい。※違う言語が紛れているとソースが読みづらくなる

　下記の様な使い分けがよいと改めて思った。
①JavaScript・・・画面周り、APIの呼び出し（ビジネスロジックは書かない）
②PHP・・・ビジネスロジックだけを書く
③ストアドプロシージャ・・・トランザクションの処理（ビジネスロジックは書かない）