# 複合的な動作確認

ここまでにインストールした様々なアプリケーションがうまく実行できる状態か検証するために、実際に簡単な Web アプリケーションを 作成して確認します。また、実際に自分たちが行う作業によってどんなものができるかを、試作品の作成を通じて学びます。

注意

このページで紹介した Web ページを絶対に一般公開しないでください。 セキュリティ的に考慮されていない箇所があるため、サーバーに不正アクセスされる恐れがあります。 契約のサーバーによっては、想定外の請求が発生する恐れがあります。手元のコンピュータのみで実験的に行う分には問題ありません。

# 摘要

  • コピー & 貼り付けで、メモができる Web サイトを作る。
  • Docker Compose を使用して PHP と MySQL 環境を用意する。
  • HTML に PHP Info を出力する処理と、データベースにアクセスする処理を書く。
  • サーバーを起動し、動作することを確認する。

# 心得

このページは、これまでの内容を踏まえて実施すれば、あなたのコンピュータの環境は整っており、コピー&貼り付けのみで何も問題なく実行可能になるようなものになっています。

うまく行かない場合は、エラー文が表示されている場合があります。 エラー文で要点を検索して、インターネット上に自分と同じような状況になっている人がいないか確認してみましょう。 ただし、コピーしようとしている HTML や PHP のコードは、あなたが意思を持って書こうとしているということは忘れないでください。 つまり、コピーするということは、あなたの発案で書いたことと同義です。よって、エラー文の中にはプログラム固有の書き方、変数名などが表示されている場合もあります。 あなたが定義したオリジナルの変数名(このページで紹介しているコードのことです)や、エラーが発生した行番号、あなたのコンピュータ固有のユーザー名で検索しても求める情報は出てきません。 エラー文を読んで、何がどのような意味で出力されているかを考察し、情報を絞り込みましょう。

# システム構成

サーバーに使用するアプリケーションは以下の通りです。

  • Web サーバー: PHP が動作する Apache
  • データベース: MySQL

下の画像は、上記のアプリケーションを使用した開発時のシステム構成です。

image

開発するにあたり、Web サーバーを実行できる環境が必要になります。 手元のコンピューターで開発を進めるため、Web の仕組み 2で解説したインターネットの仕組みを自分のパソコンの中に構築していきます。 開発環境には、Web サーバー(今回は Apache)、データベース(今回は MySQL)が必要です。 Apache は PHP が動作するような環境にします。 MySQL をわかりやすい画面で操作可能な UI ツールとして phpMyAdmin を使用します。 ソースコードは Git で管理し、リモートリポジトリとして GitHub を使用します。

これらは、コンピュータに直接インストールして使用することもできますが、ここでは Docker を使用して仮想的なコンピュータの中でサーバーを構築します。 データの管理方法としては、Apache で使用するファイルを public ディレクトリに入れ、 それらを、Apache が動作する Docker コンテナに Bind mount します。これによりファイルが共有され、開発中にファイルを変更すると、Apache コンテナにも反映されます。 また、MySQL コンテナも、データを残すために Volume を Mount します。Docker に関するボリュームの考え方や具体的な仕組みの説明は省略します。

# 動画で確認

WIN

Windows (WSL) 環境での流れを確認できます。

# ディレクトリの場所と構成

基本的に、ディレクトリを作成する場所に制約はありませんが、問題が起こりにくい場所に配置することをおすすめします。 Windows ユーザーは WSL を使用し、 Ubuntu の ユーザーディレクトリに入れてください(WSL の Windows 共有領域には入れないでください)。 Mac は任意の場所で問題ありませんが、 iCloud 等のクラウドサービスに同期しているディレクトリには配置しないでください。開発時に使用するファイルは、ファイル数が膨大になる傾向があり、 OneDrive、 Box、 iCloud などファイル同期ソフトを使用すると、同期が完了しなくなったり、不整合が発生したりする恐れがあります。ソースコードの管理は、それらに特化したツール(Git 等)を使用しましょう。 ソースコードを保存しておく場所は、自分でルールを決めておくと良いです。

ディレクトリの場所

このドキュメントでは、以下の場所で作成した状態で説明しています。

WIN

WSL: ~/github/{username}/web-sample1

(実際には /home/ubuntu/github/ts-user/web-sample1 です。)

MAC

~/github/{username}/web-sample1

(実際には /Users/tsuser/github/ts-user/web-sample1 です。)

作成するファイルの配置図

web-sample1 ディレクトリの内部は、以下のような配置となる予定です。

.
├── Dockerfile.my_php_apache
├── docker-compose.yml
├── README.md
└── public
    ├── database.php
    ├── index.php
    └── info.php

# GitHub リポジトリの作成

空のリポジトリを作成します。 GitHub にサインインした状態で、「+」アイコンから 「New Repository」 を選択します。

image

リポジトリ名は 「web-sample1」としておきます。

image

その他のオプションは変更せずに「Create Repository」をクリックしてリポジトリを作成します。

※ Private リポジトリにしても問題ありません。

image

# ローカルリポジトリのセットアップ

ターミナルでファイルを作成する場所に移動します。

コマンドの一例

image

GitHub でリポジトリを作成したときの初期画面を参考にします。「SSH」が選択されていることを確認してください。 この初期画面を参考に、コマンドを入力していきます。 慣れないうちは、一行ずつコピーして入力することをおすすめします。

image

# これはサンプルです。実際には自分で作成したリポジトリの初期画面を参考にしてください。
$ echo "# web-sample1" >> README.md
$ git init
$ git add README.md
$ git commit -m "first commit"
$ git branch -M main
$ git remote add origin git@github.com:{username}/web-sample1.git
$ git push -u origin main
1
2
3
4
5
6
7
8

コマンドの一例

image

Push 後に、 GitHub のリポジトリページを更新すると、 README.md ファイルが反映されています。

image

# Visual Studio Code を ワークスペースで起動

Visual Studio Code を開きます。ワークスペースとするディレクトリのパスを指定して起動します。 現在のパスをワークスペースとしたい場合は、 以下のようにカレントディレクトリを表す . を用いて指定できます。

$ code .
1

コマンドの一例

image

以下のように、 起点のディレクトリ名(WEB-SAMPLE1) が大文字で表示されていれば 問題ありません。

image

# 改行コードの確認

README.md をクリックして、右下の改行コードの表示が LF になっていることを確認してください。

image

# ファイルの作成

プロジェクトフォルダに、以下の 2 つのファイル(Dockerfile.my_php_apachedocker-compose.yml)を作成し、内容をコピーしてくだい。

~~/Dockerfile.my_php_apache

FROM php:8.1.2-apache
RUN apt-get update
RUN docker-php-ext-install pdo_mysql

# Install composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php --install-dir=/usr/local/bin --filename=composer
RUN php -r "unlink('composer-setup.php');"
1
2
3
4
5
6
7
8

~~/docker-compose.yml

version: '3.9'

services:
  php:
    image: my_php_apache
    working_dir: /var/www/html
    build:
      context: ./
      dockerfile: Dockerfile.my_php_apache
    # user: ${U:-root}:${G:-root}
    tty: true
    stdin_open: true
    stop_signal: SIGKILL
    restart: unless-stopped
    environment:
      DB_HOST: 'db'
      DB_PORT: '3306'
      DB_NAME: 'db1'
      DB_USER: 'user'
      DB_PASS: 'password123456'
    ports:
      - ${PORT:-8080}:80
    volumes:
      - ./public:/var/www/html

  db:
    image: mysql:8.0
    # restart: always
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: password123456
      MYSQL_DATABASE: db1
      MYSQL_USER: user
      MYSQL_PASSWORD: password123456
    # ports:
    #   - ${DB_PORT:-3306}:3306
    volumes:
      - db-data:/var/lib/mysql

  myadmin:
    image: phpmyadmin/phpmyadmin:5.1.1
    environment:
      PMA_ARBITRARY: 1
      PMA_HOST: db
      PMA_PORT: 3306
      PMA_USER: root
      PMA_PASSWORD: password123456
    depends_on:
      - db
    # restart: always
    ports:
      - ${MY_ADMIN_PORT:-8081}:80

volumes:
  db-data:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

同じ階層にpublic ディレクトリを作成します。 その中に 3 つのファイル(database.phpindex.phpinfo.php)を作成し、内容をコピーしてください。

~~/public/database.php

<?php
$dbHost = getenv('DB_HOST');
$dbPort = getenv('DB_PORT');
$dbName = getenv('DB_NAME');
$dbUser = getenv('DB_USER');
$dbPass = getenv('DB_PASS');
$dsn = "mysql:dbname=$dbName;host=$dbHost:$dbPort";
$dbh = new PDO($dsn, $dbUser, $dbPass);

if (isset($_GET['id']) && isset($_GET['action']) && $_GET['action'] === 'delete') {
  $stmt = $dbh->prepare('DELETE FROM notes WHERE id = ?;');
  $stmt->execute(array($_GET['id']));
  header('Location: ./database.php');
}

if (isset($_POST['content']) && $_POST['content'] !== "") {
  $stmt = $dbh->prepare('INSERT INTO notes (content) VALUES (?);');
  $stmt->execute(array($_POST['content']));
  header('Location: ./database.php');
}

$dbh->query('CREATE TABLE IF NOT EXISTS notes (
    id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
    content TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
  );');

$stmt = $dbh->query('SELECT * FROM notes ORDER BY created_at DESC;');
$recordLength = $stmt->rowCount();

function convertTz($datetimeText){
  $datetime = new DateTime($datetimeText);
  $datetime->setTimezone(new DateTimeZone('Asia/Tokyo'));
  return $datetime->format('Y/m/d H:i:s');
}

?>
<!DOCTYPE html>

<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Database Notes Sample</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    .text {
      width: 100%;
      height: 100px;
    }

    .card {
      border: 1px solid grey;
      padding: 16px;
      margin-bottom: 8px;
      display: flex;
      flex-direction: column;
      line-height: 1.2em;
      row-gap: 8px;
    }

    .card .title {
      font-weight: bold;
    }
  </style>
</head>

<body>
  <div style="padding:16px;max-width: 1000px;">
    <a href="/">Top page</a>
    <h1>Database Notes Sample</h1>

    <div class="card">
      <form action="./database.php" method="POST">
        <textarea name="content" class="text"></textarea>
        <button type="submit">追加</button>
      </form>
    </div>

    <?= $recordLength === 0 ? "" : "全 $recordLength 件" ?>

    <? while ($note = $stmt->fetch(PDO::FETCH_ASSOC)) { ?>
      <? $lines = explode("\n", $note['content']); ?>
      <div class="card">
        <div>
          <span class="title">内容:</span><br>
          <? foreach ($lines as $line) { ?>
            <div style="min-height:1em;">
              <?= htmlspecialchars($line); ?>
            </div>
          <? } ?>
        </div>
        <small>
          <span class="title">ID:</span> <?= $note['id'] ?><br>
          <span class="title">作成日:</span> <?= convertTz($note['created_at']) ?>
        </small>
        <div>
          <a href="./database.php?action=delete&id=<?= $note['id'] ?>">削除</a>
        </div>
      </div>
    <? } ?>

    <? if ($recordLength === 0) {  ?>
      <div class="card">アイテムなし</div>
    <? } ?>
  </div>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

~~/public/index.php

<!DOCTYPE html>

<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Index</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
    </style>
  </head>

  <body>
    <div style="padding:16px;">
      <h1>Hello PHP</h1>

      <ul style="padding-left:1.5em;">
        <li>
          <a href="/info.php">phpinfo</a>
        </li>
        <li>
          <a href="/database.php">database</a>
        </li>
      </ul>
    </div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

~~/public/info.php

<?php
phpinfo();
1
2

現在のディレクトリ構造は、このようになっているはずです。

.
├── Dockerfile.my_php_apache
├── docker-compose.yml
├── README.md
└── public
    ├── database.php
    ├── index.php
    └── info.php
1
2
3
4
5
6
7
8

画面イメージ

image

# Docker コンテナを起動する

Docker Desktop が起動していることを確認してください。

ターミナルで、プロジェクトのルートに移動し、以下のコマンド(docker compose up -d のみ)を入力します。 初回実行時は時間がかかります。

 






$ docker compose up -d
Network with-database-my_default      Created   0.1s
Volume "with-database-my_db-data"     Created   0.0s
Container with-database-my-php-1      Started   1.1s
Container with-database-my-db-1       Started   1.0s
Container with-database-my-myadmin-1  Started   1.7s
1
2
3
4
5
6

コマンドの一例

image

Docker Desktop を確認すると以下のようにコンテナが起動していることがわかります。

image

# ページにアクセスする

サーバーが起動しました。 http://localhost:8080/ (opens new window) にアクセスして確認します。 正常に起動している場合、以下の画面が表示されます。

index.php表示確認

http://localhost:8080/info.php (opens new window) にアクセスした画面です。 phpinfo() 関数は、PHP サーバーの様々な情報を HTML 形式で出力します。

info.php表示確認

http://localhost:8080/database.php (opens new window) にアクセスした画面です。

database.php表示確認

適当な値を入れて保存するとメモできます。データベースに保存されているため、ブラウザを閉じてもう一度開いても残り続けます。

image

データベース管理画面(phpMyAdmin)にもアクセスしてみてください。 http://localhost:8081/ (opens new window)

英語で表示されている場合は日本語に切り替えることができます。 Appearance settings(外観の設定)Language(言語)日本語 を選択します。

image

db1notes の順に進むと、データベースに保存されている実際の値を見ることができます。

image

# サーバの停止

以下のコマンドで Docker 環境を破棄します。 データベースのデータは残ります。

$ docker compose down
1

サーバーが正しく停止されると、localhost:8080localhost:8081 へのアクセスはできなくなります。

image

# データベースの削除(ボリュームの削除)

データベースのデータを完全に削除する場合は、 Docker のボリュームを削除します。

以下のように、Volums から 該当のボリュームのメニューを選択し、「Remove」をクリックします。

この作業はもとに戻せません。誤って必要なボリュームを削除しないように注意してください。

image

# GitHub Desktop に連携する

このプロジェクトを Git コマンドを簡単に扱えるようにする GitHub Desktop と連携します。

まず、プロジェクトディレクトリのパスを確認します。 以下のコマンドを、プロジェクトディレクトリで入力し、表示されたパスをコピーします。

WIN

Windows ユーザーは pwd コマンドではなく wslpath コマンドを使用します。 wslpath コマンドは、以下のように入力することで、 WSL 上のパスを Windows から参照したときの パスに変換して表示します。

$ wslpath -w .
1

コマンドの一例

image

MAC
$ pwd
1

GitHub Desktop で FileAdd local repository... を選択します。

image

コピーしたパスを貼り付けて Add repository をクリックします。

image

GitHub Desktop に関連付けると、以下のように差分が表示されています。

image

# GitHub のリポジトリに反映する

コミットの対象にしたいファイルを選択(すべて選択)し、「Summary (required) 」欄にコメントを入力します。

image

左下の 「Commit to main」 をクリックします。

image

下の画像のように赤枠エリアが、「Push origin 1↑」になっていることを確認し、クリック(Push)します。

image

GitHub のリポジトリを確認し、コミットしたファイルが反映されていることを確認します。

image

# さいごに

以上で複合的な動作確認は終了です。 不要であれば GitHub に作成した web-sample1 リポジトリと、今回作成した ファイルはすべて削除しても問題ありません。