注意
これは私の環境下における私の備忘録です。お使いの環境によっては内容が異なる場合があります。システム管理者でない方は絶対にやらないでください。システムにはログが残るものなので、万が一があればクビになるかもしれません。私は何も責任を負いませんのでご了承のうえご覧ください。
前置き
前回の設定編1/2と2/2の続きです。以下の内容は設定してLaravelが使えるようになった上での話になりますので、設定前の場合は先にそちらをご覧ください。
さて、LaravelでAS400のデータを引っ張れるようになればこっちのもんです。
Laravelに関する書籍や知識は少ないながらも存在するのでなんとかなりますし、Laravel.jpで検索すれば公式ドキュメントも読めます。
なので、
これは私の備忘録ですので、一から十まで手順化できません。そんなことをしたら1冊の本になってしまうので、私が気になったAS400ならではの要点だけ書きます。ご了承ください。
私が言うのもなんですが、本やドキュメントを読むなど勉強する気がなければWeb開発なんて100%無理です。
忌まわしき文字コードEBCDICの問題
まず言っておくことがあります。
Web世界の文字コードはUTF-8が基本です。HTMLもPHPもJavaScriptもjQueryもCSSもすべてUTF-8です。コード類はすべて1バイト文字ですが、コメント類や文字列は2バイト文字を使うので、Web業界の人はUTF-8で統一するように推奨されています。
聞いた話ではAS400の内部はEBCDICという訳のわからない文字コードらしいですが、ODBCで引っ張った時点では(設定次第かもしれませんが)自動的にShift-JISにしてくれます。
なのでShift-JISをUTF-8に変換しなければいけません。それはmb_convert_encoding関数を使います。
mb_convert_encoding($TABLE->COLUMN,'UTF-8','SJIS-win')
とします。$TABLEとCOLUMNはこちらが付けた名前です。キモは第3引数にSJIS-winを付けることです。
ウチだけかもしれませんが外字を使っている場合はwinが必要です。これが無いと外字は「?」で表示されます。外字を使っていなければSJISだけでもいいはずです。
ちなみにEBCDICからUTF-8への直接変換はできないそうです、ODBCとか関係なく。
最終的にはこんな感じにしています。
{{trim(mb_convert_encoding($TABLE->COLUMN,'UTF-8','SJIS-win'))}}
両端の{{}}はテンプレートエンジン(Blade)のお決まりです。trimは両端のブランク切りです。
こんな感じでHTMLに埋め込んでいます。
<table> <tr> <th>取引先コード</th><td>{{$TABLE->TCODE}}</td> </tr> <tr> <th>取引先名</th><td>{{trim(mb_convert_encoding($TABLE->TNAME,'UTF-8','SJIS-win'))}}</td> </tr> </table>
もちろんもっといいやり方があるのかもしれませんが、勉強不足のためこんな感じの実装にしています。
さらに言うと、入力した文字をAS400にWriteやUpdateする場合も注意が必要です。
Webの画面上、<input>タグなどに入力した文字はUTF-8で認識されます。
これをUPDATE文でAS400にアップしようとすると、WebとAS400を橋渡ししてくれるODBCはSJISでくるものだと思っているので、見事にバグります。
なので、
mb_convert_encoding('2バイト文字','SJIS-win','UTF-8'),
このように引数を逆にしてUTF-8からSJISに直してから、AS400にアップしてあげないといけません。
こんな感じでそのまま掃き出し、そのまま取り入れができないので、その他Laravel-Excelなど導入したツールなどもモロに影響を受けますので、調べて直さなくてはなりません。
参考までに。
MVCという仕組み
LaravelはMVCという仕組みで構築されています。MVCとはモデル(model)、ビュー(view)、コントローラー(controller)の略です。
モデルは、データベース関連
ビューは、画面表示(HTMLの画面構成)
コントローラーは、処理関係(その他色々)
解りやすくAS400開発に例えると、画面表示担当のビューがDDS(DSPFやPRTF)で、処理担当のコントローラーがCLやRPGということになります。
要するに、フラットなPHP編で説明したような1つのPHPファイルにすべてを書くのではなく、MVCの機能によって3つにファイルを分ける必要があります。
それはLaravelの掟なので従うしかありません。まぁAS400もそんな感じなので違和感はないでしょう。逆にAS400凄ぇって感心しました。
モデルはAS400で該当するものが思い当たりません。
命名規則の問題
PHPは簡単な言語のためド素人が適当に作っても動くので、クソコードが量産されてしまう難点があります。
Laravelなどのフレームワークは厳格な決まりがあり、その決まりに沿った形でつくらないといけません。
逆にその決まりに沿って作るゆえ、下手くそな人がコーディングしても、Laravelを知っている人が見れば解る。
すなわちソースコード保守性が高くなるということです。これがフレームワークのメリットの1つです。
問題はその決まりの中に、Laravelではテーブルは複数形、モデルは単数形という命名規則があります。
テーブルとはAS400でいう物理ファイルです。物理ファイルの名前は複数形で作ってくださいねと言うことです。
例えば本というテーブルならば、テーブル名は複数形なのでbooksとしなければいけないということです。
それに対してbooksテーブルを引っ張るモデルはBookというファイル名でないといけない、しかも頭は大文字とか。
そうすることによって、データベースのテーブルと、Laravelでコーディングするモデルのコードが自動的に関連づけて動くようになっているそうなのです。
Laravelは2冊勉強しましたが2冊ともそんなことが書いてありました。
これを最初聞いたときは、オイオイ無理だろそんなの・・と思いました。
既存のデータベースを使うのだから、今さらそんなこと言われてもです。
青本などの書籍のように一からテーブルを作るのと勝手が違うので戸惑いました。まずそういう事態になります。
しかも、青本にはpeopleテーブルのモデルはPersonにするとか言ってます。なぜならPersonの複数形はpeopleだからです。
こうすることによってLaravelという仕組みの中で自動的に関連付くらしいです。
あー、めんどくせぇーwwww
Laravelの本を学習していて、まずここでこりゃAS400には使えないかもなと思いました。
でも色々進めていくと、これはデータベースを引っ張るモデルだけの話だけで済みそうでした。
モデルを作るにはコマンドプロンプトでcmsフォルダに行き
C:\xampp\htdocs\cms> php artisan make:model モデル名
で作ります。
ちなみにMASTERLIBというライブラリにSYAINという物理ファイルがあったとします。
それにアクセスすべくモデルと作ろうとしたら、すでにテーブルが複数形というルールに違反します。
でも、そんなの関係ねぇーということで、
C:\xampp\htdocs\cms> php artisan make:model SYAIN
としました。するとcms\appフォルダの中にSYAIN.phpというファイルができます。これがモデルであり、このcms\app直下がモデルの置き場所です。
中身はこんな感じです
<?php namespace App; use Illuminate\Database\Eloquent\Model; class SYAIN extends Model { // }
次にコントローラーからモデルを呼び出す処理、コントローラーからビューを呼び出す処理を書いていくと言いたいところですが、このモデルはクソの役にも立ちません。
なぜなら命名規則に沿ってませんし、そもそもライブラリを指定してないのに動くはずがありません。
そりゃそうですよね。でもそんなの青本には書いていません。
その書き方はこうやります。
<?php namespace App; use Illuminate\Database\Eloquent\Model; class SYAIN extends Model { protected $connection = 'odbc'; protected $table = 'MASTERLIB.SYAIN'; }
このモデルでどのテーブルを使うのかを指定する、$table = ‘MASTERLIB.SYAIN’;
これでうまくつながります。
接続にodbcを使うことの宣言、$connection = ‘odbc’;をしていますが、これは無くても大丈夫です。なぜなら
LOG_CHANNEL=stack DB_CONNECTION=odbc DB2_HOST=マシンノアイピーアドレス DB2_USER=ユーザーアイディー DB2_PASSWORD=パスワード DB2_ODBC_name=connectionname DB2_default_table=defaulttable DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret BROADCAST_DRIVER=log
設定編で直した.envファイルの上記コード、DB_CONNECTION=odbcと、mysqlより先に宣言しているからです。
逆にmysqlの塊を先に持ってくると、$connection = ‘odbc’;の宣言なしではエラーになります。
さらに言うと、mysqlのデータベースを引っ張ってきたい場合は、ここで$connection = ‘mysql’と宣言すればいいということです。
私は$connection = ‘odbc’と高らかに宣言しておきます。念のためです。
cms\config\database.phpの頭の方に、defaultという名前で設定しましたが、これはデータベースのデフォルトとは関係ないようです。
'default' => env('DB_CONNECTION', 'odbc'),
コントローラーからモデルを指定します。今度は前回書いたtestController.php(コントローラ)を修正して使いまわします。
修正後
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\SYAIN; class testController extends Controller { public function index(){ $SYAIN = SYAIN::get(); //テーブルの中身を全部吐き出す print_r($SYAIN); } }
これで以前と同じように自身のIPアドレスにアクセスすると、確かにテーブルの内容が表示されます。
修正後コードを解説すると、
- use DB;が、use App\SYAIN;に変わっています。
- SELECT * ライブラリ名.テーブル名のSQL文が消えています。
1は、SQLを書かず、先ほど作ったSYAINモデルを使う宣言です。
2は、SYAIN::get();とするだけで、SELECT * FROM MASTERLIB.SYAINを呼び出していることになっています。
ここで1つ。LaravelにはSQL文を使わなくても、選択や射影や結合などができるようになっています。このget()もその仕組みです。
例えば条件を指定して、さらに並べ替える場合は、
$SYAIN = SYAIN::where('CODE','=','001')->orderBy('CODE')->get();
こんな感じになります。重ねるようにwhereやorderByを指定して最後にgetです。
この機能をクエリビルダとかEloquent(エロクエント)といいます。
あー、めんどくせぇーwwww
普通にSQL文の方が楽でいいのにと思いますが、
PHPコードの中にSQL文が混在しているのがスマートではない!
SQL文を覚えないといけないじゃないか!
ということで、こんな仕組みを作っているそうです。
後の処理は割愛しますが、取得したテーブル情報の$SYAINをビューに受け渡しながら、ビューを表示します。
class testController extends Controller { public function index(){ $SYAIN = SYAIN::get(); return view('test',[ 'SYAIN' => $SYAIN ]); } }
一応ビューも作ったので載せておきます。cms\resources\views\test.blade.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>社員一覧</title> </head> <body> <table border="1"> <tr> <th>コード</th> <th>名前</th> </tr> @foreach($SYAIN as $data) <tr> <td>{{$data->CODE}}</td> <td>{{trim(mb_convert_encoding($data->NAME,'UTF-8','SJIS-win'))}}</td> </tr> @endforeach </table> </body> </html>
アクセスすると、画面にこんな感じで表示されます。
Laravel/AS400でハマるポイント集みたいにしようと思いましたが、ちょっとズレてきました。
でもこれで一応MVCを一通り説明した感じになります。
後は青本にお任せします。
長々と書きましたが、この話のキモは、
- $tableでライブラリとテーブルを指定する
- 物理ファイルが複数形の命名規則になっていなくてもなんとかなる。
- 命名規則に沿っていれば自動的にやってくれることを手動で設定しないといけない。
の3つです。
使いたいときは遠慮なくSQL文を書けばいいんじゃない
およそAS400を使っている人はQUERY/400を多用していることもあり、大体の人はSQLには馴染みがあったり、使えばすぐ慣れると思っています。
SQL文は追い求めていくとQUERY/400で出来ること以外にもたくさんのことができます。
JOINしたりGROUPBYなんて当たり前、SELECT文を()でネストしたり、SELECTの結果をWHERE文の条件にしたり、IF文を使ったり、CASE文を使ったり、クロス集計などなど。
私もあまりSQLのことを解っていませんが、中途半端な形で出力してからPHPでなるべくSQLで完成形まで組み立てたほうが処理としては断然早いです。
以前はクロス集計をするときなど、多次元配列に入れなおして、組み替えたりしていましたが、処理速度は遅いです。
SQLで完結できることは、なるべくSQLで済ませておいた方が良いのだと思います。
しかし書籍で勉強していると、LaravelではなるべくSQL文を書くのではなく、クエリビルダやEloquentを使った方がいいという雰囲気を匂わせています。
でも、こんな長いSQL文を
($SQL = $SQLのように組んでるのは一度に変数に入れる文字数がエディタで制限されているようで、文字色が変わらなかったのでこうしています)
こんな感じでクエリビルダやEloquentでツギハギして実現するのは難しいというか、不可能な気がします。
$SYAIN = SYAIN::where('CODE','=','001')->orderBy('CODE')->get();
出来るのかもしれませんが、そんなときは難しいことを考えずに、SQLを書いています。それでいいのではないでしょうか。
また上の図のように、変数にSQL文を組み入れてSQLを完成させてから、DB::select($SQL)で実行してもOKです。これだとIF文も間に挟めます。
laravelは勝手にブランクのinput値をnullに置き換えるようだ
照会系だけだと寂しいので、画面に入力した文字や数字をAS400のデータベースにアップするプログラムを作っていきなりつまづいた。
<input>に何か文字が入っていればいいのだが、ブランクのときINSERTやUPDATEでアップした場合、Integrity constrait violationという訳の分からないエラーがでる。
調べてみたらSQL側のエラーで、アップするときにNULL値を渡していたようだ。
それもそのはず、AS400にNULL値はタブーなので落ちて当たり前だ。
調べてみたら、Laravelはinput値が空の場合は、nullに自動変換するという訳のわからない仕様があるようだ。
それは設定で無効にすることができます。App\Http\Kernel.phpのなかに、ConvertEmptyStringsToNullというクラスがあるので、それをコメントアウトすればいい。
以前フラットPHPでやったときはうまくいったのに・・・。そういうことはLaravelではザラにあります。
1バイトのカラムを2バイトで判定するとエラーで落ちる
AS400だけの問題なのか、またDBクラスでSQL文を書いた場合、クエリビルダやEloquentの場合もそうなのかわからないが、
<input>に入力した文字列でテーブルをwhereで検索するとき、対象のカラムが1バイトのとき(データタイプがAとか)、<input>に2バイト文字を入れてSQLを走らせるとエラーで落ちてしまう。
英数だけしか入力できないように、入力制限してあげないといけないみたい。
<input>要素のなかにpattern属性を下記のように追加してあげる。これで一応は解決。
<input type="text" pattern="^[0-9A-Za-z]+$">
入力はできるけどsubmitするとエラーが表示される。
時間時刻がおかしい
PHPの命令で
echo date(‘Y/m/d H:i:s’);
という年月日と時刻を表示する命令があるが、そのまま使うと時刻がおかしいことになる。
それはタイムゾーンの設定値。普通はphp.iniというPHPの設定ファイルを修正するのだが、Laravelは別で設定項目があるみたい。めんどくさ・・。
configフォルダの中にあるapp.phpを開き、timezoneの項目を修正する
‘timezone’ => ‘Asia/Tokyo’,
これでうまく行くはずです。
これはAS400だけの話ではありませんが、一応載せておきます。
コメント