0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HTMLとCSSとPHPを勉強したから連携させてDBにデータ格納させてみる

Posted at

こんにちは。
株式会社クラスアクト インフラストラクチャ事業部の大塚です。

最近、HTML/CSSとPHPをProgateで勉強中です。
(これで勉強することについて否定的な意見もあるかと思いますし、理由を聞くと「なるほどな」ともなります。
但し、少なくとも私のようなインフラエンジニアがフロントエンド・バックエンドの為プログラミング言語を速習するためには良いのかなぁと思ってます。)

学んでいく中でPHPを使ってHTMLからデータを受け取る方法が出てきましたので、実際にコーディングしてみようと思います。
※当方インフラエンジニアです。フロントエンド関係は勉強中です。なので詳しいことは書けません。自分用のメモ的なサムシングです。
※細かいことはこれから勉強です

環境イメージ

Win11 PCにDockerを稼働できる環境(Docker Desktop+WSL2)を用意。
docker composeでWebコンテナとDBコンテナをデプロイしていきます。
デプロイ後は8080ポートにアクセスしてWebコンテナにアクセスして、データを入力・格納できることを確認します。

growi-ページ61.drawio.png

構成

.
├── docker-compose.yaml
├── mysql
│   ├── Dockerfile
│   ├── initdb
│   │   └── 001_create_schema.sql
│   └── src
└── php
    ├── Dockerfile
    └── src
        ├── index.html
        ├── save.php
        └── style.css

コード

docker-compose.yaml

services:
  php:
    build:
      context: ./php
      dockerfile: Dockerfile
    image: myapp-php:8.4-apache
    container_name: myapp-php
    ports:
      - "8080:80"
    volumes:
      - ./php/src:/var/www/html
    environment:
      # PHP から参照する DB 接続情報
      DB_HOST: db
      DB_NAME: app_db
      DB_USER: app_user
      DB_PASS: app_pass
      DB_CHARSET: utf8mb4
    depends_on:
      db:
        condition: service_healthy

  db:
    build:
      context: ./mysql
      dockerfile: Dockerfile
    image: myapp-mysql:8.4
    container_name: myapp-mysql
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: app_db
      MYSQL_USER: app_user
      MYSQL_PASSWORD: app_pass
      TZ: Asia/Tokyo
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -prootpass || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 10

volumes:
  db-data:

php/Dockerfile

# php/Dockerfile
FROM php:8.4-apache

# PDO MySQL 拡張をインストール
# (公式 PHP イメージは docker-php-ext-install で拡張導入できます)
RUN docker-php-ext-install pdo_mysql

mysql/Dockerfile

# mysql/Dockerfile
FROM mysql:8.4

# 初回起動時に実行される初期化 SQL を配置
COPY initdb/*.sql /docker-entrypoint-initdb.d/

index.html

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="style.css">
    <title>HTML/CSS/PHP/MySQL</title>
  </head>
  <body>
    <header>
        <div class="header-container">
            <p>HTML/CSSとPHPを連携してDBにデータを格納するサイト</p>
        </div>
    </header>
    <div class="main">
        <div class="main-container">
            <form action="./save.php" method="post" autocomplete="on" novalidate>
                <div class="input-title user-name">ユーザ名</div>
                <input type="text" class="user-name-input" name="username" />
                <div class="input-title password">パスワード</div>
                <input type="password" class="password-input" name="password" />
                <div class="input-title favorite-language">好きな言語</div>
                <select class="favorite-language-input" name="favorite_language">
                    <option value="">選択してください</option>
                    <option value="JavaScript">JavaScript</option>
                    <option value="PHP">PHP</option>
                    <option value="Java">Java</option>
                    <option value="Ruby">Ruby</option>
                    <option value="Python">Python</option>
                    <option value="C#">C#</option>
                    <option value="C++">C++</option>
                    <option value="Golang">Golang</option>
                </select>
                <button class="submit-button" type="submit" >SUBMIT</button>
            </form>
        </div>
    </div>
    <footer>
        <div class="footer-container">
            <p>Copyright Since 2025 ©hogehogefugafuga. All rights reserved.</p>
        </div>
    </footer>
  </body>
</html>

style.css

/*全体*/
body{
    margin:0;
    /* 追加: sticky footer */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}
/*header*/
.header-container{
    height:80px;
    line-height: 80px;
    background-color: #1800ad;
}

.header-container p{
    margin:0;
    color:#fff;
    font-size:40px;
}
/*main*/
.main{
    /* 追加: sticky footer */
    flex: 1 0 auto;
}

.main-container{
    width:80%;
    margin:5% auto;
}

.input-title{
    font-size:50px;
}

.user-name-input,
.password-input,
.favorite-language-input{
    width:20%;
    margin-top:20px;
    margin-bottom:20px;
    font-size:20px;
}

.submit-button{
    display:block;
    background-color: #0075FF;
    font-weight: bold;
    color:#fff;
    border-radius: 20px;
    padding:10px 20px;
    margin-top:30px;
    box-shadow: 0 5px 0 0 #0F62A8;
    cursor: pointer;
}

.submit-button:active{
    box-shadow: none;
    position: relative;
    top:5px;
}

/*footer*/
.footer-container{
    background-color: #545454;
    color:#fff;
    text-align:right;
    height:50px;
    line-height: 50px;
}

.footer-container p{
    /* 追加: sticky footer */
    margin:0 10px;
    font-size: 20px;
}

save.php

<?php
declare(strict_types=1);

// エラー表示(開発時のみ)
ini_set('display_errors', '1');
error_reporting(E_ALL);

// POST以外は405
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    header('Content-Type: text/plain; charset=UTF-8');
    echo 'Method Not Allowed';
    exit;
}

// 入力取得・サニタイズ(最低限)
$username = trim((string)($_POST['username'] ?? ''));
$password = (string)($_POST['password'] ?? '');
$favorite = trim((string)($_POST['favorite_language'] ?? ''));

// サーバー側バリデーション
$errors = [];
if ($username === '') $errors[] = 'ユーザ名は必須です。';
if ($password === '') $errors[] = 'パスワードは必須です。';
if ($favorite === '') $errors[] = '好きな言語を選択してください。';

if ($errors) {
    http_response_code(422);
    header('Content-Type: text/html; charset=UTF-8');
    echo '<h1>入力エラー</h1><ul>';
    foreach ($errors as $e) echo '<li>' . htmlspecialchars($e, ENT_QUOTES, 'UTF-8') . '</li>';
    echo '</ul><p><a href="/">戻る</a></p>';
    exit;
}

// パスワードをハッシュ化(ソルトは自動付与)
$pwdHash = password_hash($password, PASSWORD_DEFAULT); // 推奨手法[15]
if ($pwdHash === false) {
    http_response_code(500);
    echo 'パスワードのハッシュ化に失敗しました。';
    exit;
}

// DB接続情報(環境に合わせて変更)
$dsn  = 'mysql:host=db;dbname=app_db;charset=utf8mb4';
$user = 'app_user';
$pass = 'app_pass';

try {
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, // 例外
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,                  // ネイティブプリペアド
    ]);

    // 追加 INSERT(プリペアドステートメント)
    $sql = 'INSERT INTO contacts (username, password_hash, favorite_language)
            VALUES (:u, :p, :f)';
    $stmt = $pdo->prepare($sql); // プリペアドで安全に実行[3][6]
    $stmt->execute([
        ':u' => $username,
        ':p' => $pwdHash,
        ':f' => $favorite,
    ]);

    // 結果表示(必要に応じてリダイレクトでもOK)
    header('Content-Type: text/html; charset=UTF-8');
    echo '<h1>保存しました</h1>';
    echo '<p>ユーザ名:' . htmlspecialchars($username, ENT_QUOTES, 'UTF-8') . '</p>';
    echo '<p>好きな言語:' . htmlspecialchars($favorite, ENT_QUOTES, 'UTF-8') . '</p>';
    echo '<p><a href="/">トップに戻る</a></p>';

} catch (PDOException $e) {
    http_response_code(500);
    header('Content-Type: text/plain; charset=UTF-8');
    echo 'DBエラー: ' . $e->getMessage();
    exit;
}

001_create_schema.sql

-- mysql/initdb/001_create_schema.sql

-- データベースを用意(Compose の MYSQL_DATABASE を使う場合でも冪等性のため明示)
CREATE DATABASE IF NOT EXISTS app_db
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

-- 対象DBを選択
USE app_db;

-- アプリ用テーブル
CREATE TABLE IF NOT EXISTS contacts (
  id                BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  username          VARCHAR(50)     NOT NULL,
  password_hash     VARCHAR(255)    NOT NULL,
  favorite_language VARCHAR(50)     NOT NULL,
  created_at        TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uq_contacts_username (username) -- ユーザ名の一意制約
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci;

実行

PS C:\Users\ohtsu\Documents\html_dev\phpでDB連携> docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
PS C:\Users\ohtsu\Documents\html_dev\phpでDB連携> docker compose up --build -d
#1 [internal] load local bake definitions
#1 reading from stdin 1.04kB 0.0s done
#1 DONE 0.0s

#2 [db internal] load build definition from Dockerfile
#2 transferring dockerfile: 185B done
#2 DONE 0.0s

#3 [php internal] load build definition from Dockerfile
#3 transferring dockerfile: 244B 0.0s done
#3 DONE 0.0s

#4 [php internal] load metadata for docker.io/library/php:8.4-apache
#4 DONE 2.2s

#5 [db internal] load metadata for docker.io/library/mysql:8.4
#5 DONE 2.4s

#6 [php internal] load .dockerignore
#6 transferring context: 2B done
#6 DONE 0.0s

#7 [php 1/2] FROM docker.io/library/php:8.4-apache@sha256:da52a29f01e7b0b07c8f6849b2564abf4c3736c490df7971484773fc95212b90
#7 DONE 0.0s

#8 [php 2/2] RUN docker-php-ext-install pdo_mysql
#8 CACHED

#9 [php] exporting to image
#9 exporting layers done
#9 writing image sha256:a1565c58c628455fbced5cfccc98fc98f7ff6a4efc79ce934dd9cf63cc641091 done
#9 naming to docker.io/library/myapp-php:8.4-apache done
#9 DONE 0.0s

#10 [php] resolving provenance for metadata file
#10 DONE 0.0s

#11 [db internal] load .dockerignore
#11 transferring context: 2B done
#11 DONE 0.0s

#12 [db 1/2] FROM docker.io/library/mysql:8.4@sha256:ad394eabaf5d728b88fd79732d94091f2165379e9b43e9748364b8c094f26e14
#12 DONE 0.0s

#13 [db internal] load build context
#13 transferring context: 76B done
#13 DONE 0.0s

#14 [db 2/2] COPY initdb/*.sql /docker-entrypoint-initdb.d/
#14 CACHED

#15 [db] exporting to image
#15 exporting layers done
#15 writing image sha256:d06f37996cfe07cb6e6653cf29e5dd438ae885a5c9f87db683fd2e86e45402c1 done
#15 naming to docker.io/library/myapp-mysql:8.4 done
#15 DONE 0.0s

#16 [db] resolving provenance for metadata file
#16 DONE 0.0s
[+] Running 6/6
 ✔ myapp-mysql:8.4         Built                                                                                                                                                                 0.0s 
 ✔ myapp-php:8.4-apache    Built                                                                                                                                                                 0.0s 
 ✔ Network phpdb_default   Created                                                                                                                                                               0.0s 
 ✔ Volume "phpdb_db-data"  Created                                                                                                                                                               0.0s 
 ✔ Container myapp-mysql   Healthy                                                                                                                                                              10.8s 
 ✔ Container myapp-php     Started                                                                                                                                                              11.1s 
PS C:\Users\ohtsu\Documents\html_dev\phpでDB連携> 
PS C:\Users\ohtsu\Documents\html_dev\phpでDB連携> docker compose ps
NAME          IMAGE                  COMMAND                   SERVICE   CREATED          STATUS                    PORTS
myapp-mysql   myapp-mysql:8.4        "docker-entrypoint.s…"   db        23 seconds ago   Up 23 seconds (healthy)   3306/tcp, 33060/tcp
myapp-php     myapp-php:8.4-apache   "docker-php-entrypoi…"   php       23 seconds ago   Up 12 seconds             0.0.0.0:8080->80/tcp, [::]:8080->80/tcp

http://localhost:8080にアクセス
image.png

こんな感じでデータが格納されたと出力された。
image.png

実際にDBを見てみる

PS C:\Users\ohtsu\Documents\html_dev\phpでDB連携> docker exec -it myapp-mysql mysql -u app_user -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 32
Server version: 8.4.6 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| app_db             |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.01 sec)

mysql> use app_db;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+------------------+
| Tables_in_app_db |
+------------------+
| contacts         |
+------------------+
1 row in set (0.01 sec)

mysql> select * from contacts;
+----+----------+--------------------------------------------------------------+-------------------+---------------------+
| id | username | password_hash                                                | favorite_language | created_at          |
+----+----------+--------------------------------------------------------------+-------------------+---------------------+
|  1 | test01   | $2y$12$kDvmMHWwW7OtX4Ow7lJSY.Y7w4.WIgscOq5uIxA4wUw9i.N2DnPue | Python            | 2025-08-16 16:22:30 |
|  2 | test02   | $2y$12$rhUkbZgBWw39yurxkl.FhOs7w3lEH7NC6e01dyGHVAitFx4REhZea | Java              | 2025-08-16 16:22:45 |
|  3 | test03   | $2y$12$AaJzWLK1Kd1ssg7Od7VdGeL4Y6O2FceStKA7L/nAMInZlypF77jgi | PHP               | 2025-08-16 16:22:58 |
+----+----------+--------------------------------------------------------------+-------------------+---------------------+
3 rows in set (0.00 sec)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?