開発環境も整ったところで、今回からは、本格的に、縦軸である販売管理の作成になります。 初心者の方には、多少難しい箇所も出てくるかと思いますが、できる限り、細かく解説したいと思っていますので、頑張ってついてきてください。 完成するころには、ネットショップや、マッチングサイトのシステムは、簡単に感じられるようになっていると思います。
それでは、システムの大まかな、おさらいをしておきましょう。
- ブラウザで動作する販売管理の作成(納品書の編集、入金伝票の入力で、請求書を発行する)
- サーバ言語はPHP、フロントエンドはJavaScript、データベースはMySQLを使用する。
内部情報は、SQLite、設定ファイルは、学習のため、xml、ini、jsonを使用する。 - インターフェースは、MDI(マルチドキュメントインターフェース)にする。
- セキュリティー強化のため、IDとパスワードはもちろん、IPアドレスでの制限も設ける。
- 不正使用防止のため、すべての操作をログに記録する。
- ディレクトリの準備
最初に、システムを作成するルートディレクトリと、バーチャルホストをご用意ください。 当サイトの、環境構築からご覧ただいている方は、shop.example.comがC:ドライブに作成されていると思いますので、 VSCodeの[ファイル]-[フォルダーを開く]で開いてください。 https://github.com/pupunzi/jquery.mb.menu - コーディング手法
筆者は、他では見たことのない、独特な開発手法を使用します。 html内に、PHPを記述するなどという、効率の悪いコーディングは、一切しません。 html内に、PHPを記述すると、ページが変わるごとに、エイチティエムエルファイルが必要ですので、ある程度の規模になると、数百個、数千個ものファイルが必要になってしまいます。 htmlファイルは、以下のtemplate.htmlのみで、必要なページは、すべてダイナミックに出力します。<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>販売管理</title> <link rel="stylesheet" href="/common/jquery-ui-1.13.1.custom/jquery-ui.css" type="text/css" /> <link rel="stylesheet" href="/common/system.css" type="text/css" /> <script type="text/javascript" src="/common/jquery-ui-1.13.1.custom/external/jquery/jquery.js"></script> <script type="text/javascript" src="/common/jquery-ui-1.13.1.custom/jquery-ui.js"></script> <script type="text/javascript" src="/common/public.js"></script> <script type="text/javascript" src="/common/system.js"></script> <script type="text/javascript"> $(document).ready(function() { $('input[type=button], input[type=submit], input[type=file], input[type=reset], button').button(); /* ready.inc */ }); $(window).on('load', function() { /* onload.inc */ }); /* javascript.inc */ </script> <style type="text/css"> /* styles.inc */ </style> </head> </head> <body> <div id="commonDialog"></div> <div id="header"> <p id="headertitle"><!-- headertitle.inc --></p> <!-- mainmenu.inc --> </div> <div id="container" class=""> <div id="main"> <!-- CGI Document.inc --> <!-- /CGI Document.inc --> </div> </div> </body> </html>
具体的には、PHPが、template.htmlを読み込み、必要な個所に、必要な 文字列や、オブジェクトを挿入して、出力します。 - 汎用ライブラリ
/common/incに汎用のライブラリ、php-lib.phpを作成します。<?php function tag_encode($buffer) { $buffer = str_replace("=", "=", $buffer); $buffer = str_replace("<", "<", $buffer); $buffer = str_replace(">", ">", $buffer); $buffer = str_replace("\"", """, $buffer); $buffer = str_replace("'", "'", $buffer); $buffer = str_replace("\t", "	", $buffer); $buffer = str_replace("\r", " ", $buffer); $buffer = str_replace("\n", "", $buffer); $buffer = trim($buffer); return($buffer); } function tag_decode($buffer, $tag = 0) { $buffer = str_replace("=", "=", $buffer); $buffer = str_replace(""", '"', $buffer); $buffer = str_replace("'", "'", $buffer); $buffer = str_replace("	", "\t", $buffer); $buffer = str_replace(" ", "\r", $buffer); if ($tag) { $buffer = str_replace("<", "<", $buffer); $buffer = str_replace(">", ">", $buffer); } return($buffer); } ?>
この関数は、文字列をエンコードする関数と、エンコードされた文字列を、 元に戻す関数です。危険な文字や、意味を持った文字コードを、一時的に無効にしています。<?php function REQUEST_Purse() { $Keys = array_keys($_REQUEST); foreach ($Keys as $key) { if (array_key_exists($key, $_FILES) || array_key_exists($key, $_COOKIE)) { unset($_REQUEST[$key]); } else if (!is_array($_REQUEST[$key])) { $_REQUEST[$key] = tag_encode(trim($_REQUEST[$key])); $_REQUEST[$key] = mb_convert_kana($_REQUEST[$key], "KV", "UTF-8"); } } } ?>
リクエストパース関数は、スーパーグローバル変数の、$_REQUESTを、より使いやすく、整えています。 通常、$_REQUEST変数は、$_GET、$_POST、$_FILE、$_COOKIE、すべてを1つに、まとめているのですが、 $_GETと$_POSTだけにして、エンコードしています。 こうすることで、送られてきたデータが、GETでもPOSTでも、スーパーグローバルの$_REQUESTで処理できます。<?php date_default_timezone_set('Asia/Tokyo'); class Application { public $name; public $cwd; public $url; public $protocol = "http"; public $method; public $phpversion; public $libversion = '20220611'; public $server; public function __construct($name = "") { $name && $this->name = $name; REQUEST_Purse(); $this->cwd = getcwd(); preg_match("/([\w\-]+)([\.\w]+)$/", basename($_SERVER['SCRIPT_NAME']), $m); !$this->name && $this->name = $m[1]; isset($_SERVER['HTTPS']) && $this->protocol .= "s"; $this->url = str_replace($_SERVER['DOCUMENT_ROOT'], "", $this->cwd); isset($_SERVER['REQUEST_METHOD']) && $this->method = $_SERVER['REQUEST_METHOD']; $this->phpversion = phpversion(); array_key_exists('SERVER_SOFTWARE', $_SERVER) && $this->server = $_SERVER['SERVER_SOFTWARE']; } } ?>
先頭の、デフォルトタイムゾーンは、時刻を東京にセットしています。 PHP内で、毎回読み込むのも面倒ですので、必ずインクルードされるライブラリに書いておきましょう。
class、Applicationは、このアプリの情報と、初期化を行います。 先ほど作成した、REQUEST_Purse関数も、このクラスがインスタンス化されたとき、呼び出されますので、 必ず、PHPの先頭でインスタンス化してください。<?php class CreateHtml { public $template; public $options = array(); public $cash = true; public $html; public function __construct($template = "") { $template && $this->template = $template; $this->template && $this->template = $_SERVER['DOCUMENT_ROOT']. $this->template; if ($this->template && is_file($this->template)) { $this->html = file_get_contents($this->template); } else { $this->html = join("\n", array( "<!DOCTYPE html>", "<html>", "<head>", " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">", " <title><!-- title.inc --></title>", " <!-- script_files.inc -->", " <!-- style_files.inc -->", " <script type=\"text/javascript\">", " $().ready(function(){", " /* ready.inc */", " });", " $(window).on('load', function() {", " /* onload.inc */", " });", " /* javascript.inc */", " </script>", " <style type=\"text/css\">", " /* styles.inc */", " </style>", "</head>", "<body>", " <!-- CGI Document.inc -->", " <!-- /CGI Document.inc -->", " <script type=\"text/javascript\">", " /* endscript.inc */", " </script>", "</body>", "</html>", )); } $this->html = preg_replace("/(\r\n|\n)/", "\r", $this->html); $this->html = preg_replace("/^\r/", "", $this->html); } public function view($content = "") { /* ssiのhtmlを挿入 cgiのssiは挿入できない */ while(preg_match("<!--#include virtual=([\"']([\w\.\-\/]+)[\"']\s*)-->/i", $this->html, $m)) { $tg = "<!--#include virtual=". $m[1]. "-->"; $ssitext = ""; $ssi = $m[2]; if ($ssi) { $url = $_SERVER['DOCUMENT_ROOT']. $ssi; if (is_file($url)) { $ssitext = file_get_contents($url); } } $this->html = str_replace($tg, $ssitext, $this->html); } /* コンテンツの挿入 */ $this->html = preg_replace( "/(<!--\s*CGI Document\.inc\s*-->).*(<!--\s*\/CGI Document\.inc\s*-->)/i", $content, $this->html); /* 細かな部品の挿入 */ while (preg_match("/(<!--|\/\*)\s*(\w+)\.inc\s*(-->|\*\/)/i", $this->html, $m)) { $v = $m[2]; !isset($this->options[$v]) && $this->options[$v] = ''; $this->html = preg_replace( "/(<!--|\/\*)\s*$v\.inc\s*(-->|\*\/)/i", $this->options[$v], $this->html); } if ($this->cash === true) { $expires = 30; header('Last-Modified: Fri Jan 01 2010 00:00:00 GMT'); header('Expires: ' . gmdate('D, d M Y H:i:s T', time() + $expires)); header('Cache-Control: private, max-age=' . $expires); header('Pragma: '); } else { /* キャッシュOFF */ header("Expires: Thu,01 Dec 1994 16:00:00 GMT"); header("Last-Modified: ".gmdate("D,d M Y H:i:s")." GMT"); header("Cache-Control: no-cache,must-revalidate"); header("Cache-Control: post-check=0,pre-check=0",false); header("Pragma: no-cache"); } header("Content-type:text/html;charset=utf-8"); echo str_replace("\r", "\n", $this->html); } } ?>
このCreateHtmlクラスが、今回の肝です。
PHPで生成されたドキュメントや、オブジェクトを、テンプレートの適正な位置に挿入して、 htmlにして出力するクラスです。 8行目の、コンストラクタで、テンプレートのパスを受け取り、フルパスを絶対パスに変換して、テキストファイルとして読み込んでいます。 当チャンネルでは、ドキュメントルートからのパスをフルパス、サーバの先頭、windowsなら、C:\からを絶対パス、カレントディレクトリからを相対パスと、統一して呼びます。
インスタンス時に、テンプレートが指定されていない場合は、15行目から、簡易的なhtmlを生成しています。
49行目の、ビューメソッドで、エイチティエムエルを出力しています。 52行目では、テンプレートにSSIが設定されている場合は、指定されているファイルをインクルードしています。 この場合のSSIは、テキストファイルでなければなりません。コマンドの処理は行っていません。 66行目で、Document.inc間に、コンテンツを挿入して、70行目からループで、細かな部品を挿入しています。 これは、テンプレート内に、コメントで、変数名.incが見つかれば、対応するキーの$options変数を割り当てています。 たとえば、<!-- mainmenu.inc -->が見つかれば、$optionsのキーが、'mainmenu'の値と置き換えます。 したがって、テンプレート内に、自由に作成したコメントキーワードを、PHP内で$optionsに値をセットすれば、 どこにでも、何個でも挿入可能だということです。
77行からは、作成されたhtmlを、ブラウザのキャッシュに残すか、残さないかを指定しています。$html = new CreateHtml(); $html->view("ここに本文");
これだけでHTMLを出力できます。ページのタイトルを指定するには、$html = new CreateHtml(); $html->options = array('title'=>'ひも爺の実践プログラミング'); $html->view("ここに本文");
作成したオブジェクトのoptionsプロパティに配列を指定して設定します。 - 共有ライブラリ
次に、すべてのPHPからインクルードされる、とは言っても、今回は、index.phpと、job.phpだけなのですが、 初期設定や、共通の関数、クラスを記述する、PHPを作成しておきます。 commonのインク内に、public.phpを作成して、こちらのコードを記述します。<?php ini_set('display_errors', "On"); $app = new Application(); $template = "/common/template.html"; ?>
2行目は、開発中は、エラーを表示させるよう設定、3行目で、アプリを初期化、4行目で、テンプレートのパスを設定しています。 パスは、ドキュメントルートからのフルパスですので、必ず / から始まっていなければなりません。
このファイルを、index.php、job.phpからインクルードします。 - index.php
こちらがシステムにアクセスされると、最初に表示されるindex.htmlです。<?php include "common/inc/php-lib.php"; include "common/inc/public.php"; if ($error) { header('HTTP/1.1 403 Forbidden'); exit; } $doc =<<<_ <iframe src="job.php" name="job" id="job" frameborder="0" style="margin:0;width:100%;height:100px;"></iframe> _; $html = new CreateHtml($template); $options['headertitle'] = '<p id="headertitle">販売管理</p>'; $options['mainmenu'] = '<div id="menubar">メインメニューを挿入</div>'; $options['toolbar'] = '<div id="toolbar">ツールバーを挿入</div>'; $options['ready'] = " $('#header').css('display', 'block'); windowGoResize(); window.onresize=windowGoResize;"; $options['javascript'] = " function windowGoResize() { var h = $(window).height(); $('#job').css('height', h - (parseInt($('#header').css('height'), 10) + 6)); }"; $html->options = $options; $html->view($doc); ?>
最初に、汎用ライブラリphp-lib.phpを、次に、共有のライブラリpublic.phpをインクルードしています。 public.phpで、エラーの出力宣言や、アプリの初期化Application()を行っていますので、個々のphpでは、設定する必要はありません。
$options['headertitle']は、タイトルバーを設定。$options['mainmenu']は、メインメニューの領域を用意し、$options['toolbar']は、 ツールバーの領域を用意しています。options['ready']は、ページが表示されたとき、最初に実行されるJavaScriptで、 ブラウザのサイズに合わせて、フレームのサイズを動的に調整しています。
27行目で、すべてのオプションを$htmlオブジェクトのoptionsプロパティに渡しています。 - job.php
今後、ほぼすべての動作は、この、job.phpで行います。<?php include "common/inc/php-lib.php"; include "common/inc/public.php"; if ($error) { header('HTTP/1.1 403 Forbidden'); exit; } $html = new CreateHtml($template); $options['ready'] = ""; $html->options = $options; $html->view(""); ?>
今のところ、これだけですが、フレームから呼び出されるphpになります。
ここまでをブラウザからアクセスすると、このように表示されます。
次回は、メインメニューとツールバーを作成します。
#004:ソースコード
実際のソースコードと、学習時に表示されたコードは、デバッグや表示の便宜上異なる場合があります。
予めご了承ください。