Webアプリケーションを作成しよう

テクノロジー・ガジェット

Webアプリケーションの作り方をメモに残します。

開発基本の順序:設計→テーブル作成→HTML→ロジック処理→CSS

Webアプリケーションを作成

  1. 「登録ページ」HTMLで表示する
  2. 「登録ページ」ログを登録する
  3. 「登録ページ」CSSでスタイルを整える
  4. 「トップページ」HTMLで表示する
  5. 「トップページ」ログの一覧を表示する
  6. 「トップページ」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の順番でプログラミングをするのがおすすめ

  1. HTML 要素を表示
  2. PHP ロジックを実装
  3. CSS 見た目を整える

HTMLを書くときの順序

  1. WFを書く
  2. HTMLで基本構成とheadを作成する
  3. 大枠のレイアウトをメモする
  4. HTMLで大枠のレイアウトを作成する
  5. 個別のパーツをメモする
  6. HTMLで個別のパーツを作成する

PHPを書くときの順序

ロジックを一つずつ実装する

CSSを書くときの順序

  1. 大枠のレイアウトをスタイリングする
  2. 大枠のレイアウトをレスポンシブ対応する
  3. 個別パーツをスタイリングする
  4. 個別パーツをレスポンシブ対応する

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された情報
      • 連想配列として受け取る
  • $_GET
    • URLパラメータの情報
      • 連想配列として受け取る
  • $_SERVER
    • サーバーや実行時の環境情報
      • 連想配列として受け取る
      • REQUEST_METHOD:ページにアクセスする際に使用されたリクエストのメソッド名。GET, POSTなど

マジック定数を使おう

使われる場所によって値が変化する定数がマジック定数

  • __DIR__
    • そのファイルの存在するディレクトリ名
  • __FILE__
    • そのファイルのフルパスとファイル名

外部ファイルを読み込もう

共通の処理は別ファイルに1箇所にまとめて、そのファイルを読み込んで使おう

  • require
    • 指定したファイルを読み込む
      • 読み込みが失敗すると処理がそこで中断される
      • require ‘somefile.php’;
  • require_once
    • requireとほぼ同じだが、一度しかファイルを読み込まない
      • 悩んだらrequire_onceを使うことを推奨
      • require_once ‘somefile.php’;

「登録ページ」ログを登録する

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は関数の読み込みなどに使用する

コメント