サンプルソリューションのデータは、新しいサンプルデータを作る を参照してください。
この記事で使用するソースコードは、PHP 化の叩き台にする HTML ソースコード 以下にある次のソースコードです。
- edit-record.html
- bootstrap-common.css
- fmda-form.css
- fmda-form-check.js
利用環境
以下の環境を前提に説明しています。
- Claris FileMaker Pro 21.1.1.41 macOS
- Claris FileMaker Server 21.1.1.40 Ubuntu 22(AMD)
- サーバ: Ubuntu 22.04.5 LTS
- SSL 証明書
- リクエストする側のサーバ(任意)
開発段階では、以下を使用した方が楽でしょう。
Web ページの作成は、以下を条件とします。
- Bootstrap v5.3.3
- Bootstrap Examples Checkout を土台にしています。
- PHP 8.2.22
Checkout から edit-records.html、そして index.php へ
ここまで、元になっている Bootstap Exampls の Checkout から、edit-record.html を作成しました。edit-record.html で読み込まれている css/js も元々、Checkout に含まれている要素をスタートラインにしています。
- bootstrap-common.css(Bootstrap Examples 全体で共通している部分を抜き出した)
- fmda-form.css(Checkout に含まれる checkout.css と同一)
- fmda-fmda-form-check.js(Checkout に含まれる checkout.js と同一)
この構成を踏襲して、Checkout の UI/UX のブロックごとの要素を次のように使用することを考えました。
- フォーム → 新規作成・編集フォーム 兼 削除・複製レコードデータ表示
- カートの内容表示 → レコード情報表示
- プロモコード有効化フォーム → レコード ID でレコードを取得するフォーム
- フッタ → フッタ
以上の方針で、作ったのが edit-records.html で、現在の状態で submit すると、当然ですが、フォームチェックが効くだけで、何も起こりません。
現在の状態から、PHP コードにしていくことを考えます。まず、edit-records.html を複製し、index.php とファイル名を変更します。今後、この index.php を扱っていきます。
前回も説明していますが、index.php の内容は、大きく分けて次のようになります。
- Bootstrap v5.3.3 と Checkout のコンテンツを表示するための枠組み
- Checkout コンテンツ
前者は、新規作成・編集フォーム、削除・複製レコードデータ表示のどのリクエストでも完全に同じものが必要です。
後者は、同じデザインを使用しますが、処理は異なります。
このように分ければ、まず、前者の部分を PHP コード化してしまった方が良さそうです。例として、index.php の <body>
タグまでの PHP コード化をやってみます。
HTML begin
まず、HTML の始まりの部分を考えてみます。
index.php 1〜15行目
<!DOCTYPE html>
<html lang="ja" data-bs-theme="auto">
<head>
<script src="https://getbootstrap.com/docs/5.3/assets/js/color-modes.js"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="FileMaker Data API テスト 及び サンプル">
<meta name="author" content="">
<title>records - Edit Record</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="../css/bootstrap-common.css">
<!-- Custom styles for this template -->
<link href="../css/fmda-form.css" rel="stylesheet">
</head>
<body class="bg-body-tertiary">
このうち、変数にした方が良さそうな部分を抜き出します。
<script src="https://getbootstrap.com/docs/5.3/assets/js/color-modes.js"></script>
<meta name="description" content="FileMaker Data API テスト 及び サンプル">
<meta name="author" content="">
<title>records - Edit Record</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="../css/bootstrap-common.css">
<link href="../css/fmda-form.css" rel="stylesheet">
最後の ..css/fmda-form.css
以外は単純なスカラー変数でいいでしょう。
<link href="../css/fmda-form.css" rel="stylesheet">
この部分は、将来、読み込むスタイルシートが増えるかもしれないということを考えて、配列にしておいた方が良さそうです。
それでは、HTML を出力する前に変数を設定して、HTML をヒアドキュメントとして、変数を埋め込むという形で、書き換えてみます。
<?php
$color_modes_js = 'https://getbootstrap.com/docs/5.3/assets/js/color-modes.js';
$description = 'FileMaker Data API テスト 及び サンプル';
$author = '';
$title = 'records - Edit Record';
$bootstrap_css = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css';
$bs_css_integrity = 'sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH';
$bs_common_css = '../css/bootstrap-common.css';
echo <<<_HTML_
<!DOCTYPE html>
<html lang="ja" data-bs-theme="auto">
<head>
<script src="{$color_modes_js}"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{$description}">
<meta name="author" content="{$author}">
<title>{$title}</title>
<link href="{$bootstrap_css}" rel="stylesheet" integrity="{$bs_css_integrity}" crossorigin="anonymous">
<link rel="stylesheet" href="{$bs_common_css}">
<!-- Custom styles for this template -->
_HTML_;
$custom_css = ['../css/fmda-form.css'];
for ($i = 0; $i < count($custom_css); $i++) {
echo "<link href=\"{$custom_css[$i]}\" rel=\"stylesheet\">\n";
}
echo <<<_HTML_
</head>
<body class="bg-body-tertiary">
_HTML_;
?>
現在の index.php
は、HTML への PHP コード差し込みなので、閉じタグ ?>
が必要なことを忘れないようにしましょう。
また、変数名は、直感的に分かるものを選びましょう。その場の思いつきで省略するよりも冗長な方が、後で見た時にわかりやすいと思います。
このような方法で、ソースコード全体を変更していくわけですが、ヒアドキュメントの HTML が混ざっていると、ソースコード全体の流れが掴みにくくなります。
元々、HTML は冗長な上に、間に PHP を差し込んでいくわけですから、さらに長くなり、4K ディスプレイを縦に3台並べたくなりますが、首がつかれるだろうということは容易に想像がつきます。
解決方法の一つとして、HTML のソースコードを吐き出す部分は、すべてクラスのスタティックメソッドにしてしまって、直接、参照する形にすることです。
新たに php/
ディレクトリを作って、クラスファイルを作ってしまいましょう。
php/bs-frame-class.php
<?php
class BsFrame {
static function HtmlBegin (
string $color_modes_js,
string $description,
string $author,
string $title,
string $bootstrap_css,
string $bs_css_integrity,
string $bs_common_css,
array &$custom_css
): void
{
echo <<<_HTML_
<!DOCTYPE html>
<html lang="ja" data-bs-theme="auto">
<head>
<script src="{$color_modes_js}"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{$description}">
<meta name="author" content="{$author}">
<title>{$title}</title>
<link href="{$bootstrap_css}" rel="stylesheet" integrity="{$bs_css_integrity}" crossorigin="anonymous">
<link rel="stylesheet" href="{$bs_common_css}">
<!-- Custom styles for this template -->
_HTML_;
for ($i = 0; $i < count($custom_css); $i++) {
echo "<link href=\"{$custom_css[$i]}\" rel=\"stylesheet\">\n";
}
echo <<<_HTML_
</head>
<body class="bg-body-tertiary">
_HTML_;
}
}
ヒアドキュメントの HTML を全部抜き出し、BsFrame
クラスの HtmlBegin
メソッドとしました。
こうすることで、index.php
の該当部分は、以下のようになります。
index.php 該当部分
<?php
require_once(__DIR__ . '/php/bs-frame-class.php');
$color_modes_js = 'https://getbootstrap.com/docs/5.3/assets/js/color-modes.js';
$description = 'FileMaker Data API テスト 及び サンプル';
$author = '';
$title = 'records - Edit Record';
$bootstrap_css = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css';
$bs_css_integrity = 'sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH';
$bs_common_css = '../css/bootstrap-common.css';
$custom_css = ['../css/fmda-form.css'];
BsFrame::HtmlBegin($color_modes_js, $description, $author, $title, $bootstrap_css, $bs_css_integrity, $bs_frame_css, $custom_css);
?>
随分、すっきりしましたが、このパターンで、残りのコードを PHP 化していくと、変数設定部分とスタティックメソッド呼び出し部分に収斂しそうです。
変数設定もメインプログラムに置かなくてもいいものが多そうですので、これも変更してしまいましょう。
php/preferences.php
<?php
// for HtmlBegin()
$color_modes_js = 'https://getbootstrap.com/docs/5.3/assets/js/color-modes.js';
$description = 'FileMaker Data API テスト 及び サンプル';
$author = '';
$title = 'records - Edit Record';
$bootstrap_css = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css';
$bs_css_integrity = 'sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH';
$bs_common_css = '../css/bootstrap-common.css';
$custom_css = ['../css/fmda-form.css'];
これで、index.php
の該当部分は、以下のようになります。
index.php 該当部分 (2)
<?php
require_once(__DIR__ . '/php/bs-frame-class.php');
require_once(__DIR__ . '/php/preferences.php');
BsFrame::HtmlBegin($color_modes_js, $description, $author, $title, $bootstrap_css, $bs_css_integrity, $bs_frame_css, $custom_css);
?>
この部分は大分、すっきりしました。また、処理内容もパッと見でわかります。
さらに、prefernces.php
も見直しましょう。
ここには秘匿すべき情報は一つもありません。また、気ままに変更したい $description
や $title
、Bootstrap のアップデートで変わる情報も含まれています。
こういうものは、FileMaker ユーザなら、FileMaker Pro で管理したくなります。
FileMaker で設定した内容が、Web ページに反映されると便利です。
この辺りを見据えて、秘匿する必要のない情報は、JSON にしてしまいます。
json/
ディレクトリを新たに作って、そこにこの情報を置きましょう。
json/html-contents.json
{
"contents" : {
"colorModesJs" : "https://getbootstrap.com/docs/5.3/assets/js/color-modes.js",
"description" : "FileMaker Data API テスト 及び サンプル",
"author" : "",
"title" : "records - Edit Record",
"bootstrapCss" : "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
"bsCssIntegrity" : "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH",
"bsFrameCss" : "./css/bootstrap-common.css",
"customCss" :
[
{
"location" : "./css/fmda-form.css"
}
]
}
}
この JSON ファイルを読み込むために、関数を用意して、php/ ディレクトリに置きます。
php/php-functions.php
<?php
function json_to_array(string $json_object): array
{
$json_object = mb_convert_encoding($json_object, 'UTF-8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
return json_decode($json_object, true);
}
function json_get_element(string $json_file): array
{
$json_object = file_get_contents($json_file, true);
return json_to_array($json_object);
}
JSON 読み込みに対応するため、preferences.php を書き換えます。
php/preferences.php (2)
<?php
$html_contents_json_file = __DIR__ . '/../json/html-contents.json';
$temp_array = json_get_element($html_contents_json_file);
// for HtmlBegin(), LoadColorModesSvg(), ColorModes()
$color_modes_js = $temp_array['contents']['colorModesJs'];
$description = $temp_array['contents']['description'];
$author = $temp_array['contents']['author'];
$title = $temp_array['contents']['title'];
$bootstrap_css = $temp_array['contents']['bootstrapCss'];
$bs_css_integrity = $temp_array['contents']['bsCssIntegrity'];
$bs_frame_css = $temp_array['contents']['bsFrameCss'];
$custom_css = [];
foreach ($temp_array['contents']['customCss'] as $key => $value) {
array_push($custom_css, $value['location']);
}
JSON の contents.customCss[] 配列は、最低 1個ということで書いてありますが、0個に対応するならば、preference.php
の foreach
内でチェックしてください。
以上で、index.php は php-functions.php
を読み込むように変わります。
index.php (3)
<?php
require_once(__DIR__ . '/php/bs-frame-class.php');
require_once(__DIR__ . '/php/php-functions.php');
require_once(__DIR__ . '/php/preferences.php');
BsFrame::HtmlBegin($color_modes_js, $description, $author, $title, $bootstrap_css, $bs_css_integrity, $bs_frame_css, $custom_css);
?>
以上の方針で、Bootstrap、Checkout の骨格部分をすべて、PHP コード化してしまいます。
php/bs-frame-class.php (2)
<?php
class BsFrame {
static function HtmlBegin (
string $color_modes_js,
string $description,
string $author,
string $title,
string $bootstrap_css,
string $bs_css_integrity,
string $bs_common_css,
array &$custom_css
): void
{
echo <<<_HTML_
<!DOCTYPE html>
<html lang="ja" data-bs-theme="auto">
<head>
<script src="{$color_modes_js}"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{$description}">
<meta name="author" content="{$author}">
<title>{$title}</title>
<link href="{$bootstrap_css}" rel="stylesheet" integrity="{$bs_css_integrity}" crossorigin="anonymous">
<link rel="stylesheet" href="{$bs_common_css}">
<!-- Custom styles for this template -->
_HTML_;
for ($i = 0; $i < count($custom_css); $i++) {
echo "<link href=\"{$custom_css[$i]}\" rel=\"stylesheet\">\n";
}
echo <<<_HTML_
</head>
<body class="bg-body-tertiary">
_HTML_;
}
static function LoadColorModesSvg ( ): void
{
echo <<<_HTML_
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
<symbol id="check2" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
</symbol>
<symbol id="circle-half" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/>
</symbol>
<symbol id="moon-stars-fill" viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
<path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"/>
</symbol>
<symbol id="sun-fill" viewBox="0 0 16 16">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</symbol>
</svg>
_HTML_;
}
static function ColorModes (
string $name = 'テーマ切り替え',
string $light = 'ライト',
string $dark = 'ダーク',
string $auto = '自動'
): void
{
echo <<<_HTML_
<div class="dropdown position-fixed bottom-0 end-0 mb-3 me-3 bd-mode-toggle">
<button class="btn btn-bd-primary py-2 dropdown-toggle d-flex align-items-center"
id="bd-theme"
type="button"
aria-expanded="false"
data-bs-toggle="dropdown"
aria-label="Toggle theme (auto)">
<svg class="bi my-1 theme-icon-active" width="1em" height="1em"><use href="#circle-half"></use></svg>
<span class="visually-hidden" id="bd-theme-text">{$name}</span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="bd-theme-text">
<li>
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
<svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="#sun-fill"></use></svg>
{$light}
<svg class="bi ms-auto d-none" width="1em" height="1em"><use href="#check2"></use></svg>
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="false">
<svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="#moon-stars-fill"></use></svg>
{$dark}
<svg class="bi ms-auto d-none" width="1em" height="1em"><use href="#check2"></use></svg>
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto" aria-pressed="true">
<svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="#circle-half"></use></svg>
{$auto}
<svg class="bi ms-auto d-none" width="1em" height="1em"><use href="#check2"></use></svg>
</button>
</li>
</ul>
</div> <!-- / .dropdown -->
_HTML_;
}
static function HtmlEnd (
string $bootstrap_js,
string $bs_js_integrity,
array &$custom_js
): void
{
echo "<script src=\"{$bootstrap_js}\" integrity=\"{$bs_js_integrity}\" crossorigin=\"anonymous\"></script>\n";
for ($i = 0; $i < count($custom_js); $i++) {
echo "<script src=\"{$custom_js[$i]}\"></script>\n";
}
echo <<<_HTML_
</body>
</html>
_HTML_;
}
}
php/preferences.php (3)
<?php
$html_contents_json_file = __DIR__ . '/../json/html-contents.json';
$temp_array = json_get_element($html_contents_json_file);
// for HtmlBegin(), LoadColorModesSvg(), ColorModes()
$color_modes_js = $temp_array['contents']['colorModesJs'];
$description = $temp_array['contents']['description'];
$author = $temp_array['contents']['author'];
$title = $temp_array['contents']['title'];
$bootstrap_css = $temp_array['contents']['bootstrapCss'];
$bs_css_integrity = $temp_array['contents']['bsCssIntegrity'];
$bs_frame_css = $temp_array['contents']['bsFrameCss'];
$custom_css = [];
foreach ($temp_array['contents']['customCss'] as $key => $value) {
array_push($custom_css, $value['location']);
}
// for HtmlEnd()
$copyright_year = $temp_array['contents']['copyrightYear'];
$copyright_holder = $temp_array['contents']['copyrightHolder'];
$link_items = $temp_array['linkItems'];
$bootstrap_js = $temp_array['contents']['bootstrapJs'];
$bs_js_integrity = $temp_array['contents']['bsJsIntegrity'];
$custom_js = [];
foreach ($temp_array['contents']['customJs'] as $key => $value) {
array_push($custom_js, $value['location']);
}
json/html-contents.json
{
"contents" : {
"colorModesJs" : "https://getbootstrap.com/docs/5.3/assets/js/color-modes.js",
"description" : "FileMaker Data API テスト 及び サンプル",
"author" : "",
"title" : "records - Edit Record",
"bootstrapCss" : "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
"bsCssIntegrity" : "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH",
"bsFrameCss" : "./css/bootstrap-common.css",
"customCss" :
[
{
"location" : "./css/fmda-form.css"
}
],
"copyrightYear" : "2025",
"copyrightHolder" : "FileMaker Data API を使う",
"bootstrapJs" : "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js",
"bsJsIntegrity" : "sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz",
"customJs" :
[
{
"location" : "./js/fmda-form-check.js"
}
]
}
}
index.php (3)
<?php
require_once(__DIR__ . '/php/bs-frame-class.php');
require_once(__DIR__ . '/php/php-functions.php');
require_once(__DIR__ . '/php/preferences.php');
BsFrame::HtmlBegin($color_modes_js, $description, $author, $title, $bootstrap_css, $bs_css_integrity, $bs_frame_css, $custom_css);
BsFrame::LoadColorModesSvg();
BsFrame::ColorModes();
?>
〜 <div class="container"> から </div> <!-- / .container --> まで省略 〜
<?php
BsFrame::HtmlEnd($bootstrap_js, $bs_js_integrity, $custom_js);
これで、共通の骨組み部分はできました。
<div class="container">
から </div> <!-- / .container -->
までが、コンテンツ部分になります。
次回は、FileMaker Data API のリクエストを除いた、コンテンツ部分を暫定的に PHP 化します。