Webアプリケーションの作り方をメモに残します。
開発基本の順序:設計→テーブル作成→HTML→ロジック処理→CSS
Webアプリケーションを作成
- テーブルをPHPから作成
- Composerとは?
- 環境変数を管理しよう
- HTML、CSS、PHPの作成手順
- PHPでHTMLを表示しよう
- フォームを作成しよう
- データを登録しよう
- エラーログを出力しよう
- Bootstrapを使おう
- 「登録ページ」HTMLで表示する
- 「登録ページ」ログを登録する
- 「登録ページ」CSSでスタイルを整える
- 「トップページ」HTMLで表示する
- 「トップページ」ログの一覧を表示する
- 「トップページ」CSSでスタイルを整える
テーブルをPHPから作成しよう
companiesテーブルを存在していたら削除して、新たにcompaniesテーブルを作成する
//まずは大枠のイメージを先にコーディングする <?php $link = dbConnect(); //DBに接続 dropTable($link); //テーブルの削除 createTable($link); //テーブルの作成 mysqli_close($link); //DBの切断
利用する関数の大枠を先にコーディングする
<?php function dbConnect() { //DBに接続して$linkを返す } function dropTable($link) { //テーブルを削除する } function createTable($link) { //テーブルを作成する } $link = dbConnect(); dropTable($link); createTable($link); mysqli_close($link);
関数の中身をコーディングする
<?php function dbConnect() { $link = mysqli_connect('db', 'book_log', 'pass', 'book_log'); if (!$link) { echo 'Error:データベースに接続できません' . PHP_EOL; echo 'Debugging error:' . mysqli_connect_error() . PHP_EOL; exit; } return $link; } function dropTable($link) { $dropTableSql = 'DROP TABLE IF EXISTS companies;'; $result = mysqli_query($link, $dropTableSql); if ($result) { echo 'テーブル削除が完了しました' . PHP_EOL . PHP_EOL; } else { echo 'Error: テーブルの削除に失敗しました' . PHP_EOL; echo 'Debugging error:' . mysqli_error($link) . PHP_EOL; } } function createTable($link) { $createTableSql = <<<EOT CREATE TABLE companies( id INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, name VARCHAR(255), founder VARCHAR(255), establishment_date DATE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) DEFAULT CHARACTER SET=utf8mb4; EOT; $result = mysqli_query($link, $createTableSql); if ($result) { echo 'テーブル作成が完了しました' . PHP_EOL . PHP_EOL; } else { echo 'Error: テーブルの作成に失敗しました' . PHP_EOL; echo 'Debugging error:' . mysqli_error($link) . PHP_EOL; } } $link = dbConnect(); dropTable($link); createTable($link); mysqli_close($link);
Composerとは?
現状の課題
ソースコードにログイン情報はベタ書きしているため、ソースコードを見られたときにセキュリティリスクがある。
ライブラリを使用して、機密データを別途管理する仕組みを取り入れる
ライブラリとは
ライブラリとは使いたい部品が入っている道具箱、特定の機能を提供するコードをひとまとめにしたもの
PHPでライブラリを使おう
PHPでライブラリを導入したいときはComposerを使うと便利
ComposerはPHPの依存管理ツール
Pythonでいうところのpipと同じ依存管理ツール
Composerの仕組み
composer.json と composer.lock ファイルで依存するライブラリを定義する
composer.json
プロジェクトで使用するライブラリを一覧にしたもの
composer.lock
実際にどのバージョンをダウンロードしたのかひとまとめにしたもの
composer installを実行すると
インストールしたバージョン情報がcomposer.lockファイルに書き出される
すでにcomposer.lockファイルがある場合、lockファイルに書かれているライブラリのバージョンがインストールされる
チームでライブラリのバージョンを統一できて、バージョンが違うことで起こるエラーを回避できる
Composerを使おう
init
対話形式でプロジェクトの初期に一度実行する。composer.jsonファイルが生成される。
composer init
require
新しいライブラリを追加する。composer.jsonにライブラリが、composer.lockに実際にインストールしたものが記載される。
composer require <ライブラリ名>
install
composer.jsonもしくはcomposer.lockに従ってインストールする
composer install
remove
ライブラリを削除する
composer remove <ライブラリ名>
環境変数を管理しよう
機密データをソースコードとは別で管理しよう
機密データを分離して管理するために環境変数を使おう
通常の変数
通常の変数では中身が見えている
$name = 'sample'
環境変数
環境変数では中身を隠すことができる。OSに格納される変数となる
DB_NAME = 'sample'
PHPで環境変数を使おう
環境変数を扱うには .env ファイルを使うと便利(phpdotenv)
現状
$link = mysqli_connect('db', 'book_log', 'pass', 'book_log');
環境変数を使うと
$dbHost = $_ENV['DB_HOST'];
$link = mysqli_connect($dbHost, ...);
phpdotenvを使いたい
まずはcomposer initを実行する
docker-compose exec app composer init
- Package name—エンター
- Description—エンター
- Author—nを入力してエンター
- Minimum Stability—エンター
- Package Type—エンター
- License—エンター
- Would you like to define your dependencies—エンター
- Search for a package—エンター
- Would you like to define your dev dependencies—エンター
- Search for a package—エンター
- Do you confirm generation—yesと入力してエンター
srcの下にcomposer.jsonファイルが作られているのを確認
phpdotenvをインストール
githubで「phpdotenv」と検索する
READMEのInstallationを参考にappコンテナでインストールする
$ docker-compose exec app composer require vlucas/phpdotenv
インストールが実行されてcomposer.lockファイルが作られているのを確認する
.envファイルを作成する
ルートディレクトリ直下に.envファイルを作成して環境変数を保存する
DB_HOST="db" DB_USERNAME="book_log" DB_PASSWORD="pass" DB_DATABASE="book_log"
テーブル作成のコードに以下を追記
<?php # /../..は親の親ディレクトリを指定 require __DIR__ . '/../../vendor/autoload.php'; #autoloadでライブラリを読み込み function dbConnect() { # /../..は親の親ディレクトリを指定、以下2行で環境変数の読み込み準備 $dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../..'); $dotenv->load(); #以下で環境変数の代入 $dbHost = $_ENV['DB_HOST']; $dbUsername = $_ENV['DB_USERNAME']; $dbPassword = $_ENV['DB_PASSWORD']; $dbDatabase = $_ENV['DB_DATABASE']; $link = mysqli_connect($dbHost, $dbUsername, $dbPassword, $dbDatabase);
HTML、CSS、PHPの作成手順
HTML→PHP→CSSの順番でプログラミングをするのがおすすめ
- HTML 要素を表示
- PHP ロジックを実装
- CSS 見た目を整える
HTMLを書くときの順序
- WFを書く
- HTMLで基本構成とheadを作成する
- 大枠のレイアウトをメモする
- HTMLで大枠のレイアウトを作成する
- 個別のパーツをメモする
- HTMLで個別のパーツを作成する
PHPを書くときの順序
ロジックを一つずつ実装する
CSSを書くときの順序
- 大枠のレイアウトをスタイリングする
- 大枠のレイアウトをレスポンシブ対応する
- 個別パーツをスタイリングする
- 個別パーツをレスポンシブ対応する
PHPでHTMLを表示しよう
phpファイルに最低限のコードを記述して動作確認をする
<?php ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>テストタイトル</title> </head> <body> <p>Hello World</p> </body> </html>
apacheが起動していれば localhost:<ポート番号>/test.phpでアクセスして動作確認をしてみる。
フォームを作成しよう
レイアウトをメモしよう
WFを作成して、紙でいいのでレイアウトをメモして入れ子構造を最初に整理しておこう
フォームの補足
<form action="create.php" method="post"> <!-- actionは登録ボタンを押した後に移動するページ methodはデータの送信方法。GETかPOSTを指定。登録系はPOSTを指定 --> <label for="name">会社名</label> <!-- forはパーツid属性と同じ値にすることでパーツとラベルを関連付けできる --> <input type="text" name="name" id="name"> <!-- input type="text"で1行のテキストエリアを設置する nameはパーツの名前。PHPにデータを送信する際の項目名になる --> <button type="submit">登録する</button> <!-- submitはデータを送信 --> </form>
ラジオボタンの補足
<form action="create.php" method="post"> <div> <input type="radio" name="sex" id="male" value="male"> <label for="male">男性</label> <!-- inputのidとlabelのforの値を同じにすることで項目とボタンを連動 nameを下のdivと同じにするとグループ化される --> </div> <div> <input type="radio" name="sex" id="female" value="female"> <label for="female">女性</label> </div> <button type="submit">登録する</button> </form>
実際にフォームを作成してみる
<?php ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>会社情報の登録</title> </head> <body> <hi>会社情報の登録</hi> <form action="" method="POST"> <div> <label for="name">会社名</label> <input type="text" id="name" name="name"> </div> <div> <label for="establishment_date">設立日</label> <input type="date" name="establishment_date" id="establishment_date"> </div> <div> <label for="">代表者</label> <input type="text" name="founder" id="founder"> </div> <div> <button type="submit">登録する</button> </div> </form> </body> </html>
実際にコーディングする際はdivごとにコーディングして挙動を確認する
「登録ページ」HTMLで表示する
<?php ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>読書ログ登録</title> </head> <body> <h1>読書ログ</h1> <form action="" method="post"> <div> <label for="title">書籍名</label> <input type="text" name="title" id="title"> </div> <div> <label for="author">著者名</label> <input type="text" name="author" id="author"> </div> <div> <label>読書状況</label> <div> <div> <input type="radio" name="status" id="status1" value="未読"> <label for="status1">未読</label> </div> <div> <input type="radio" name="status" id="status2" value="読んでる"> <label for="status2">読んでる</label> </div> <div> <input class="form-check-input" type="radio" name="status" id="status3" value="読了"> <label for="status3">読了</label> </div> </div> </div> <div> <label for="score">評価(5点満点の整数)</label> <input type="number" name="score" id="score"> </div> <div> <label for="summary">感想</label> <textarea type="text" name="summary" id="summary" rows="10"></textarea> </div> <button type="submit">登録する</button> </form> </body> </html>
データを登録しよう
PHPの定義済み変数を使おう
- $_ENV
- 環境変数
- 連想配列として受け取る
- 環境変数
- $_POST
- POSTされた情報
- 連想配列として受け取る
- POSTされた情報
- $_GET
- URLパラメータの情報
- 連想配列として受け取る
- URLパラメータの情報
- $_SERVER
- サーバーや実行時の環境情報
- 連想配列として受け取る
- REQUEST_METHOD:ページにアクセスする際に使用されたリクエストのメソッド名。GET, POSTなど
- サーバーや実行時の環境情報
マジック定数を使おう
使われる場所によって値が変化する定数がマジック定数
- __DIR__
- そのファイルの存在するディレクトリ名
- __FILE__
- そのファイルのフルパスとファイル名
外部ファイルを読み込もう
共通の処理は別ファイルに1箇所にまとめて、そのファイルを読み込んで使おう
- require
- 指定したファイルを読み込む
- 読み込みが失敗すると処理がそこで中断される
- require ‘somefile.php’;
- 指定したファイルを読み込む
- require_once
- requireとほぼ同じだが、一度しかファイルを読み込まない
- 悩んだらrequire_onceを使うことを推奨
- require_once ‘somefile.php’;
- requireとほぼ同じだが、一度しかファイルを読み込まない
「登録ページ」ログを登録する
formのactionを空で作成していた箇所にaction=”create.php”と入力して、create.phpファイルを作成する。まずはコメントで処理の流れを記述する
<?php // HTTPメソッドがPOSTだったら // POSTされた会社情報を変数に格納する // バリデーションする // データベースに接続する // データベースにデータを登録する // データベースとの接続を切断する
<?php // HTTPメソッドがPOSTだったら var_dump($_SERVER['REQUEST_METHOD'] === 'POST'); if ($_SERVER['REQUEST_METHOD'] === 'POST') { // POSTされた会社情報を変数に格納する // バリデーションする // データベースに接続する // データベースにデータを登録する // データベースとの接続を切断する }
$_SERVER[‘REQUEST_METHOD’]から’POST’が返ってくるのをまずは確認。確認できたらコメントを削除する
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST') { // POSTされた会社情報を変数に格納する var_export($_POST); // バリデーションする // データベースに接続する // データベースにデータを登録する // データベースとの接続を切断する } // 次のデータが表示される array ( 'name' => 'aa', 'establishment_date' => '2022-06-24', 'founder' => 'aa', )
$_POSTでデータが送られてくるので中身をまずは確認する
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST') { // POSTされた会社情報を変数に格納する $company = [ 'name' => $_POST['name'], 'establishment_date' => $_POST['establishment_date'], 'founder' => $_POST['founder'] ]; var_export($company); // バリデーションする // データベースに接続する // データベースにデータを登録する // データベースとの接続を切断する }
$companyのデータをvar_exportで確認する
データベースに接続する
共通する処理を再利用するために、フォルダとファイルを用意する。companies/lib/mysqli.php
<?php //mysqli.phpの中身 require __DIR__ . '/../../../vendor/autoload.php'; function dbConnect() { $dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../../..'); $dotenv->load(); $dbHost = $_ENV['DB_HOST']; $dbUsername = $_ENV['DB_USERNAME']; $dbPassword = $_ENV['DB_PASSWORD']; $dbDatabase = $_ENV['DB_DATABASE']; $link = mysqli_connect($dbHost, $dbUsername, $dbPassword, $dbDatabase); if (!$link) { echo 'Error:データベースに接続できません' . PHP_EOL; echo 'Debugging error:' . mysqli_connect_error() . PHP_EOL; exit; } return $link; }
<?php //元々dbConnect()メソッドが書いてあったファイルをrequire_onceに置き換える require_once __DIR__ . '/lib/mysqli.php'; function dropTable($link) { $dropTableSql = 'DROP TABLE IF EXISTS companies;'; $result = mysqli_query($link, $dropTableSql); if ($result) { echo 'テーブル削除が完了しました' . PHP_EOL . PHP_EOL; } else { echo 'Error: テーブルの削除に失敗しました' . PHP_EOL; echo 'Debugging error:' . mysqli_error($link) . PHP_EOL; } }
<?php // create.phpの中身 require_once __DIR__ . '/lib/mysqli.php'; function createCompany($link, $company) { $sql = <<<EOT INSERT INTO companies ( name, establishment_date, founder ) VALUES ( "{$company['name']}", "{$company['establishment_date']}", "{$company['founder']}" ) EOT; $result = mysqli_query($link, $sql); if ($result) { echo '登録が完了しました' . PHP_EOL; } else { echo 'Error : データの登録に失敗しました' . PHP_EOL; echo 'Debugging error : ' . mysqli_error($link) . PHP_EOL; } } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $company = [ 'name' => $_POST['name'], 'establishment_date' => $_POST['establishment_date'], 'founder' => $_POST['founder'] ]; // バリデーションする $link = dbConnect(); createCompany($link, $company); mysqli_close($link); }
リダイレクト処理を追加しよう
リダイレクトとは別のページに遷移させる仕組みのこと
PHPでリダイレクト処理をするにはheader関数を使用する
header($header); //$headerはヘッダー文字列
header("Location: http://www.example.com");
リダイレクト処理をすると以降のコードは実行されないので注意。通常は末尾に追記する。
エラーログを出力しよう
エラーログを出力するためには、error_log関数を使う
error_log($message);
// データベースに接続できませんとログを残すなら error_log("データベースに接続できません");
エラーログを確認するには
docker logs コマンドを使用してエラーログを確認
#コンテナ名を確認 docker ps
#ログを確認したいとき # -fはfollowのことで、ログを表示し続ける docker logs -f <コンテナ名>
#アクセスログを確認したいとき docker logs <コンテナ名> -f 2>/dev/null
#エラーログを確認したいとき docker logs <コンテナ名> -f 1>/dev/null
エラーログの例
$result = mysqli_query($link, $sql); if (!$result) { error_log('Error : fail to create company'); error_log('Debugging error : ' . mysqli_error($link)); }
#appコンテナ名を確認 docker ps #ログの出力 docker logs <コンテナ名> -f 1>/dev/null
バリデーション処理をしよう
if ($_SERVER['REQUEST_METHOD'] === 'POST') { $company = [ 'name' => $_POST['name'], 'establishment_date' => $_POST['establishment_date'], 'founder' => $_POST['founder'] ]; $errors = validate($company); //以下バリデーション処理 if (!count($errors)) { $link = dbConnect(); createCompany($link, $company); mysqli_close($link); header("Location: index.php"); } }
// バリデーションする関数 function validate($company) { if (!strlen($company['name'])) { $errors['name'] = '会社名を入力してください'; } elseif (strlen(['name'] > 255)) { $errors['name'] = '会社名は255文字以内で入力してください'; } return $errors; }
// 今回はcreate.phpにバリデーションエラーがあった場合の処理をハードコーディングしている ?> <!DOCTYPE html> <html lang="ja"> <head> ... </head> <body> <hi>会社情報の登録</hi> <form action="create.php" method="POST"> <?php if (count($errors)) : ?> <ul> <?php foreach ($errors as $error) : ?> <li> <?php echo $error ?> </li> <?php endforeach; ?> </ul> <?php endif; ?> ... </form> </body> </html>
日付のバリデーション処理
$dates = explode('-', $company['establishment_date']); if (!strlen($company['establishment_date'])) { $errors['establishment_date'] = '設立日を入力してください'; } elseif (count($dates) !== 3) { $errors['establishment_date'] = '設立日を正しい形式で入力してください'; } elseif (!checkdate($dates[1], $dates[2], $dates[0])) { $errors['establishment_date'] = '設立日を正しい日付で入力してください'; }
explodeで日付を分割して、checkdateで存在する日付か確認している
PHPとHTMLファイルを分割しよう
includeを使って指定したファイルを読み込む
include 'sample.php';
require_onceは関数の読み込みなどに使用する
コメント