MAMPのエラーのはなし

MySQLのアップグレードをしようとしたときに起こったエラー

MAMPを起動させると、MySQLのアップグレードを促してきたので試したところ、エラーになってしまいました。

アップグレード前のMySQLのバージョンは5.7.30です。
(アップグレードしてなるはずだったバージョンは不明)

ひとまず閉じるとMAMPウィンドウの右上の電源アイコンがオレンジ色で「stop」と表示されました。
(「cloud」の雲アイコンが赤いのは前からだったはず)

f:id:yokoyoko_115:20211012004006p:plain
MAMPの画面

これがどういう状況なのか謎のまま、MySQLのアップグレードができそうな操作
[Tools > Upgrade MySQL databases...] を試しに押してみました。

すると再び同じエラーメッセージが出ました。

f:id:yokoyoko_115:20211012004635p:plain
エラー画面

エラーメッセージ

mysql_upgrade: Got error: 2002: Can't connect to local MySQL server through socket '/Applications/MAMP/tmp/mysql/mysql.sock' (61) while connecting to the MySQL server Upgrade process encountered error and will not continue.

mysql_upgrade: Got error: 2002: MySQLサーバーへの接続中に、ソケット「/Applications/MAMP/tmp/mysql/mysql.sock」を介してローカルのMySQLサーバーに接続できません(61)。 アップグレード処理でエラーが発生し、続行できません。

以下は、MAMPがうまく使えるようになるまでにあれこれしたことを挙げていきます。

MySQLサーバーが起動しているか確認

エラーメッセージから調べるとこちらの記事を見つけました。

[2002] MySQLのソケットエラー の原因と対処法 | 株式会社ビヨンド

まずはmysqldのプロセスがあるかどうか ps(process)コマンドを使って確認します。
プロセスがあればすなわちそのアプリケーション、mysqldが実行中か確認できるようです。

プロセスもちゃんと知っておいたほうが良さそうですね。

% ps aux | grep mysqld と入れると次のように返ってきました。

% ps aux | grep mysqld
username         3327   0.0  0.0  4259000    248 s000  U+   12:05PM   0:00.00 grep mysqld
username         3325   0.0  0.0        0      0   ??  Z    12:05PM   0:00.00 (mysqld)

これは起動していないっぽいです。ステータスの「Z」はゾンビ状態だそうです。

% sudo /etc/init.d/mysqld status

これでもMySQLサーバーの起動はできず。パスワードrootを入れてもできませんでした。3回パスワードを間違えて怖くなったのでやめました。
そもそも「init.d」ディレクトリが見つかりませんでした(この頃はいろいろと間違っている...ひどい過ち)。
※後ほど再チャレンジしました(後述)。

mysql.sockの確認

次に /Applications/MAMP/tmp/mysql/ を確認しました。

% ls /Applications/MAMP/tmp/mysql/
mysql.pid        mysql.sock     mysql.sock.lock

目視(Finder)では見つからなかったのにmysql.sockがあるようです。

MyQSLサーバーを起動させる

ソケット云々がなくてもMyQSLサーバーは起動させられるのだろうか、MyQSLサーバーが起動しないとそもそも接続の段階に移らないのではないか?
など考えてMyQSLサーバーを起動させる方法を調べてやってみました。

ためしたこと1:ログを削除してみる

MAMPでMySQL Serverが突然起動しなくなった時 → ログファイル削除 | シンプルシンプルデザイン

/Applications/MAMP/db/mysql57
ここにあるib_logfile0、ib_logfile1というファイルを削除してみます。怖いのでバックアップを取ります。

しかしMAMPを起動しても何も変わりませんでした。

ためしたこと2:my.confの作成

MacのMAMPのMySQLServerが起動しない時 - Qiita

/Applications/MAMP/conf
こちらに参考記事をもとにmy.cnfファイルを作りました。
こちらもMAMPを起動しても変わりませんでした;;
作成したmy.confは削除しました。

順番はMySQLの接続→起動??

今度はMySQLサーバーへの接続がうまくいっていないために起動しないのかなと思いました。
復習、MySQLとはデータベース管理システムの1つです。データベースを使うためのソフトウェアです。

MySQLサーバーの起動」とは、MySQLを起動させてサーバーにアクセスできるようにすること...?
起動はどのタイミングで行われるのか?

いろいろ考えても答えが出ないので引き続きあれこれしました。

MySQLにログインする

調べたら出てきました。
新しい行程「ログイン」。接続・起動よりは前っぽい印象です。

MAMPを使っていたらおそらくMAMP起動時にされていたのであろうMySQLのログインをコマンドラインから行ってみます。

調べたら % mysql -u root
でログインできます。とあるのですが「command not found: mysql」と返ってきました。

ターミナルでMySQLを使う準備〜Mac用 | IT工房|AI入門とWeb開発

そうでした。
mysqlを実行するパスが通っていないので、mysqlのあるところまで移動して実行する必要があります。

cd /Applications/MAMP/Library/bin/
% ./mysql -u root -p

すると最初に出てきたあのエラー!

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/Applications/MAMP/tmp/mysql/mysql.sock' (61)

ということは、MySQLにログインする時点でつまづいていた、ソケットに問題があるということなのでしょうか。

もう一度、ソケットファイルの確認。

MySQL mysql.socketがどこにあるか探したい。 - かもメモ

こちらに書かれている mysql_config コマンドでソケットファイルの場所が確認できるそうです。 /Applications/MAMP/Library/bin/ にはいろいろ実行ファイルがあって試してみたいです。

↑このディレクトリで
% ./mysql_config --socket
すると
/Applications/MAMP/tmp/mysql/mysql.sock

こう出ました。 やはりソケットファイルはあるみたいです。

ここまでのあれこれをまとめると、推測ですが、
my.cnfを作る

mysql.sockが生成される

mysql.sockがちゃんと使われているとMySQLが起動する

ということなのではないでしょうか。

試しに、再チャレンジとして、今度はパスに気をつけてMyQSLサーバー起動のコマンド % ./mysqld start
を打ってみました。

エラーの嵐!

最終的に
[Note] ./mysqld: Shutdown complete
とシャットダウンしていました。恐ろしい。
(サーバーの状態を確認する % ./mysqld status でも同じでした)

やはりソケットファイルの問題のようです。

mysql.sockが目視できないのが引っかかる

やっぱり実は実態がないのではないか(アップグレード処理の拍子にmysql.sockが作られたと勘違いされている)と思い、コマンドラインから空のmysql.sockファイルを作ってみます。

% touch mysql.sock

しかし何も変わらず。見えません。

ところでmysql.sock.lockとは?

どうしてここまで気に留めなかったのか。mysql.sock.lockを調べてみます。

MySQL5.7が起動できない。 - Qiita

この記事を読んでmysql.sock.lockファイルを削除してみよう、

とすると、VS Codeで開いてみたタイミングだったのか、mysql.sock.lockが消えていました!あとmysql.pidファイルもです。

ここでMAMPを起動してみました。

すると、オレンジの「stop」ボタンがグレーの「start」ボタンになっていました。 めでたくMAMPWordPressのサイトを見ることができました。

後で確認すると、MAMPが起動していないときはmysql.sock.lockファイルもなく、MAMP起動後にmysql.sock.lockが作られているようです。

またまた勝手な推測ですが、おそらくmysql.sock.lockでアップグレードのうまくいかないmysql.sockの設定がロックされてしまい、接続できなかったのではないでしょうか。

根本的な解決は、

MySQLのコンフィグファイルとクライアントソフトのコンフィグファイルのソケットを同じパスに設定する

なのだと思います。
多分。仕組みが謎です。

せめてコマンドラインからmysql.sockを開いてみればよかったー(遅い)

クライアントソフトはMAMPのことなのか、それぞれのファイルがどこにあるのか、ごちゃごちゃしてきました。

結局解決方法は何だったのか

ちゃんと道筋を立てたMAMPMySQLサーバー起動の流れは分からずじまいでした。

ここまで調べて偶然元に戻った印象です。

また、MyQSLサーバーの起動きっかけだったのか、MAMP画面の「start」ボタンを押して少しすると、半ば強制的にPCが再起動しました。 お、恐ろしい。

結局MyQSLのアップグレードも叶わず。作業の途中だったので変な操作しないようにしています。
このままアップグレードできなかったらのちのち困るはずです。

どうしたものか。

後に同じ問題になった時に、少しでも以前の出来事が問題改善、知識のアップデートにつながるといいです。

ひとまず様子見ます。おしまい。

Hugo - ファビコン、ロゴ、サイト名の変更

はてなブログからHugoを使ったブログの移行が全然進んでいないので、また少しずつ進めようと思います。

Hugoのテーマによって仕様が異なると思うので自分メモです。

もとのテーマ:clarity

Clarity | Hugo Themes

☆今回の作業をやった後に見つけた記事です。同じような方がいて嬉しい♪

Hugoテーマの選定(clarity)と表示カスタマイズ | damのブログラミング雑記ブログ

ファビコンの変更

svgファイルでファビコンを作りました。
svgファイルが表示されない場合に備えてpngファイルも用意しました。

<link rel="icon" type="image/svg+xml" href="http://localhost:1313/icons/favicon.svg">
<link rel="icon alternate" type="image/png" href="http://localhost:1313/icons/favicon.png">

このコードをどこに書くのか、ファビコンのファイルをどこに置くのかを探しました。

themeディレクトリの外に置いてファイルの上書きをする

/themes/hugo-clarity の中のファイルを編集しても、scssファイル同様、テーマ元が更新されれば書き換わってしまいます。
なのでtheme内の該当ファイルをコピーして、配置しました。

/themes/hugo-clarity/layouts/partials/favicon.html このファイルを
/layouts/partials/favicon.html
ディレクトリを作ってここへ置きました。

partialsディレクトリに入っている他の各htmlファイルも同じようにコピーしてthemeの外に置いてテーマのカスタマイズができるようです。

favicon.html

{{- $iconsDir := default "icons/" .Site.Params.iconsDir }}
{{- $appleTouch := absURL (printf "%s%s" $iconsDir "apple-touch-icon.png") }}
{{- $favicon := absURL (printf "%s%s" $iconsDir "favicon.svg" ) }}
{{- $faviconAlt := absURL (printf "%s%s" $iconsDir "favicon.png" ) }}
{{- $manifest := absURL (printf "%s%s" $iconsDir "site.webmanifest" ) }}
<link rel="apple-touch-icon" sizes="180x180" href="{{ $appleTouch }}">
<link rel="icon" type="image/svg+xml" href="{{ $favicon }}">
<link rel="icon alternate" type="image/png" href="{{ $faviconAlt }}">
<link rel="manifest" href="{{ $manifest }}">

今度はファビコン画像の置く場所を確認します。

/themes/hugo-clarity/static/icons にファビコンやアイコン関係の画像がいろいろありました。
favicon.icoもあったのですが、↑のコードには書かれていない様子。どこかで使われているのでしょうか。

ひとまず変更後の apple-touch-icon.pngfavicon.svgfavicon.png
/static/icons へ置きました。

favicon.svgが表示されない

はじめChromeで表示されませんでした。コードの src のパスを叩いても表示されるのに、デベロッパーツールのNetworkをみてもリクエストされていない。
Safariでは表示されるのにChromeで表示できない...「Can I use」を見てもChromeは対応しているはずなのに謎でした。
ローカル環境だからなのかとも考えました。

結局他のロゴやサイト名変更などいじっている間に表示されるようになりました。謎の時差。

ロゴの変更

もとのテーマではヘッダーとフッターにそれぞれロゴが表示されています。

partialsディレクトリのheader.html、footer.htmlを見てみると該当の部分がありました。
さらにheader.htmlには

{{- partialCached "logo" (dict "logo" $logo "class" "nav_hide") }}

とあったので、logo.htmlが使われていることがわかりました。

フッターの方は、ロゴファイル名の変更(ロゴもpngからsvgに変更しました)、ロゴファイルの参照先はファビコンと同じ場所、ということがわかりました。

ヘッダーは少しややこしそうでした。

header.html(一部抜粋)

{{- $params := .Site.Params }}
{{ $logo := $params.logo }}

{{- partialCached "logo" (dict "logo" $logo "class" "nav_hide") }}

変数を見て、.Site.Params.logo でロゴの何かを設定しているのだろうと推測しました。

/themes/hugo-clarity/exampleSite/config/_default/params.tomlは「Params」と関係していそうなので、その中の「logo」部分を探すと、

logo = "logos/logo.png"

すごくこれっぽいです。このファイルをコピーして
/config/_default/params.toml を作って logo = "logos/logo.svg に書き換えました。

ヘッダーのlogo.svgが表示されない

またまたChromeで表示されませんでした。不思議なことにフッターのロゴは表示されましたがヘッダーが表示されませんでした。

ロゴファイルの参照先はあっていて、デベロッパーツールのNetworkを見てもリクエストされているのに、Elementsで見ると「レンダリングサイズ」が0x0になっていました。

そこで「レンダリング」で調べると、imgタグに width``height を指定しているとレイアウトシフトが起こらなくなるという記事が出てきて、レンダリングにも関係するかと思い設定してみました。
するとレンダリングもされて表示されました。

またもや謎でした。フッターと何が違うのか。こちらも時間が経てば表示されるものなのかもしれません。

サイト名の変更

title やロゴ画像の alt をサイト名に変えたいです。
サイト名はきっと同じところで管理されているのだと思います。

Site.Title が見つからない

head.htmlやheader.htmlを見る限りでは、サイト名は .site.Title で設定されているようです。

head.html(一部抜粋)

{{- $title := "" }}

<title>{{ with $title }}{{ . }} {{ $separator }} {{ end }}{{ .Site.Title }}</title>

「Params」のパターンと同じであればtitle.tomlというファイルですが、ありませんでした。
「title」や「Clarity」でファイル内検索をして /config.toml を見つけました。

title = "Clarity"

こちらを変更しました。

しかしなぜか変わらない

<title> の中身を直接入れてしまえば出るのですが、トップページ以下のページが上手く出し分けできなくなってしまうので困りました。

多言語のtitle設定

さらに「Clarity」でファイル内検索をすると、/config/_default/languages.toml が見つかりました。

languages.toml

[en]
  title = "Clarity"
  LanguageName = "English"
  weight = 1

[pt]
  title = "Claridade" # just for the sake of showing this is possible
  LanguageName = "Português"
  weight = 2

weight = 1 の英語のtitle表示が優先されていたようです。

英語もポルトガル語も対応させる予定はありません。

試しに[ja] を作って他をコメントアウトしたところ、多言語選択のボタンから「Japanese」を選択すると /jaがurlに入るようになりました。

記事がなく真っ白になりました。

ファイルごと消してしまっていいのかよく分からないので、ひとまずすべてコメントアウトして取っておくことにしました。

そうするとconfig.tomlの title の値が無事反映されました。

今後の課題・調べること

探り探りでやっとロゴとサイト名が変更できました!

このまま進めていくには限界があるので以下を調べつつカスタマイズしていきます。

  • exampleSiteディレクトリは何に使われているか
    もしかするとthemeディレクトリの中にカスタマイズ用のテーマを作って、その中にsiteディレクトリ、という構成なのかもと思い始めています。
    ルートディレクトリにあれこれ置くのも、テーマやサイトの切り替えがなければ問題ないといいのですが。
  • Hugoの文法を理解しよう
    以前もGo言語を少し調べましたがすっかり忘れました。with とかif文のような文法がわからないとどうにもならなさそうです。
  • 英語を読もう
    ファイル内のあちこちにに英語でコメントがあるので、大事なヒントを拾っていきます。
    英語を地道に読んでいくことが近道になりそうです。

同時進行で各メニューやカテゴリの表示など整えていきたいです。

SVG画像の使い方

使うことが増えてきたSVG画像。
曖昧だった部分をまとめました。

SVGとは

SVG(Scalable Vector Graphics)です。
拡大縮小できるベクター画像

SVGベクトル形式なのに対し、JPEGPNG、GIFはビットマップ形式ラスター画像です。

Illustratorに出てくる「ラスタライズ」は、パスデータや埋め込み・リンク画像をラスター画像にします。

Photoshopにに出てくる「ラスタライズ」は、テキストレイヤーやシェイプレイヤー、レイヤースタイルなどをラスター画像にします。

パスになっているものはSVGに書き出せます。
(※テキストはそのままSVGに書き出せるのですが、パス情報ではなくテキストやフォントの情報が入っています)

SVGの書き出しから使い方まで詳しい記事です。

SVGを使うときに知っておくといいことをまとめました - Qiita

こちらも色々網羅されています。

HTML SVG の基本的な使い方 / Web Design Leaves

SVGといえば!詳しいページです。

svg要素の基本的な使い方まとめ

viewPortとviewBox

svg要素の属性である width, heightviewBox
考え出すといつも大混乱します。

viewPort(ビューポート) viewBox(ビューボックス)
意味 表示領域 描画領域
書き方(例) width="400" height="300" ※cssで指定するなら省略可 viewBox="0 0 400 300"
単位 単位が付けられる。ない場合はpx 単位は付けられない。利用単位(「座標系」における単位)

widthheight 属性は、img要素の同属性とほぼ同じと考えてよさそうですが、問題は viewBox 属性。

ビューポートとビューボックスの幅と高さの数値が一致していればわかりやすいのですが、違っていると途端にわからなくなります。

そこでこんな言葉を見つけました。

【SVG 基礎 vol.1】SVGとは・viewPortとviewBox・基本の書き方・埋め込み方法 - KDE BLOG

SVGにおける1座標(利用単位)は、width、heightをviewBoxで定義したX軸、Y軸のそれぞれの値で割ったサイズということになります。

ここは割り算が発生するんですね。

そこで以下のように考えるとわかりやすいのではないかと思いました。

  1. viewBoxの中の描画を考える
  2. それをviewPortに収める

viewBoxの中の世界は単位のない世界なので、まずは座標を結んで描画し、それを単位のあるviewPortに落とし込むイメージです。

f:id:yokoyoko_115:20210627025823j:plain

preserveAspectRatio

属性の名前です。意味は「アスペクト比の保持」です。
上に書いた収めるというのは、viewPortとviewBoxの縦横比が同じであれば、拡大・縮小してきっちり収まります。
縦横比が異なる場合は、preserveAspectRatio 属性の値によって収め具合が変わってきます。
性質的にviewbox属性を持っている要素にある属性だと思っています。

SVGの描画領域 - Qiita

preserveAspectRatio="xMidYMid meet" 「ビューポートの上下左右中央から縦横の比率を守って拡大縮小する」がデフォルトだそうです。
設定値がたくさんありますね。

少し考えてみました。

f:id:yokoyoko_115:20210627231421j:plain

数値を入れて確かめてみよう

こちらでviewPortとviewBoxの値を入れて確かめることができます。

SVG viewBox 確認サンプル / Web Design Leaves

できることならviewPortとviewBoxの数値を揃えて作りたいものです。

SVG内で使われている要素

svg要素の中でしかお目にかからない要素が出てくるので、未だに馴染みが薄いです。
その中でもよく目にする要素を調べました。

大方こちらに書かれています。

SVGの記述方法 - Qiita

全部はこんなにあります!

SVG 要素リファレンス - SVG: Scalable Vector Graphics | MDN

g要素

「g = group」。
svg内の各要素をグループ化するための要素です。
イラレで作ったものをsvgで保存する際オブジェクトをグループ化していると、svgファイルのソースではそのまま <g> でオブジェクト(<path><polygon>など)が囲まれています。

単純な1つのオブジェクト(円)だけならSVGの書き出しで <g> は付かないだろうと思いきや、

<g id="レイヤー_2" data-name="レイヤー 2">
  <g id="レイヤー_1-2" data-name="レイヤー 1">
    <circle class="cls-1" cx="46" cy="46" r="45.5"/>
  </g>
</g>

2重に付いていました。

1つのレイヤーに円を作っただけなのですが、謎です。

まとめて色をつけたい、変形させたいときなどは <g> で囲って設定します。
不要なg要素は削除していいと思います。

後述してますがg要素でもuse要素で参照(呼び出し)することができます。

path要素

パスで結んだ図形の要素です。
名前のついている図形(円や多角形など)はそれぞれ要素(<circle><polygon> など)が用意されています。
<path> では円などを描画できますが、主に複雑な図形を表します。

d属性

d - SVG: Scalable Vector Graphics | MDN

dは何の略なのだろう、というのが気になりました。
おそらく

d属性は描かれるパスを定義します。

とあるので、「Definition(定義)」のdなのではと推測しました。

値は直線や曲線などを示すコマンドを使って座標を打っていくのですが、手動でここをいじることはまずないと思うので、ざっくりMやZなどを知っておこうと思います。

polygon要素、circle要素

それぞれ多角形、円の要素です。楕円はellipse要素です。

points属性

polygon(多角形)、polyline(折れ線)要素で使います。x、y座標を入れます。
d属性と似ていますが、コマンドがなく、座標を直線で結びます。
pointsの値は見てなんとなく理解できそうです。

symbol要素

「symbol(記号、符号、マーク)」
use要素でオブジェクト(図形)を表示させるために使う要素で、オブジェクトを定義します。
symbol要素で囲ったオブジェクトは表示されません。

symbol要素はviewbox属性とpreserveAspectRatio属性を持っているので、symbol要素を使うときは、その親のsvg要素でviewbox属性を指定するよりはsymbol要素で指定した方が使い勝手がよいです。
両方でviewbox属性指定するとややこしいことになります。

こちらで検証されています。

SVGのsymbol、use要素とviewBox属性の挙動 - Kekeの日記

svg要素の中にはsymbol要素でオブジェクトを複数定義することができます。

そしてsymbol要素の中でのg要素の使い所は、symbol要素の中でまとめてスタイルを掛けたい(動き、色など)ときに意識して囲っていけばいいのかなと思います。

use要素でオブジェクトを参照するため、symbolまたはg要素に id を付けます。

defs要素

「Definitions(定義)」。これも「定義」なのではないかと思います。

symbol要素同様、defs要素で囲ったオブジェクトは表示されません。

defs - SVG: Scalable Vector Graphics | MDN

参照される要素は、可能なかぎりdefs要素内で定義されることが推奨されています。

一口に「参照」といっても、下に書いたuse属性以外にも参照の形があります。

上記のNDMのページを見てみると、

fill="url(#Gradient01)

参照元linearGradient というグラデーションを定義する要素です。

オブジェクトの「定義」もsymbol要素以外にlinearGradient要素など色々あるので、とりあえずdefs要素で囲っておきましょう。

<defs><linearGradient></linearGradient></defs>
<defs><symbol></symbol></defs> でいいはずです。

defs または symbol で囲われていないものも見かけるので、今後もいろいろコードを見て確認します。

また、定義部分が表示されないように一工夫必要です。

インラインSVGのdefsとgかsymbolとuseによる呼び出しのマークアップ

インラインSVGの呼び出しのマークアップは画像を定義する呼び出し元を表示したくない場合はデザインのCSSで親要素のsvgタグなどに非表示の「display:none」をかける。

defsタグやsymbolタグを使うとその場で描画しなくなるけれどもマークアップした分の高さが残ってしまうから余計な隙間を縮めるためにもCSSの「display:none」で要素として取り除くか、せめて「height:0」で高さをなくす必要が出て来る。

svg要素に display="none" をかけて非表示にする方法も見かけました。display属性があるのですね。

別記事で検証しました。

use要素

symbol要素で定義したオブジェクトを参照します。

オブジェクトの定義、参照は、それぞれ別のsvg要素で囲って使うことがほとんどです。

使い方(例)

<svg><use href="#idname" /></svg>

use要素のhref属性で定義元を参照します。定義が別ファイルで書かれていれば、リンク先のパスも書きます。

この href は、symbol要素で囲っていないid属性が付いているg要素のオブジェクトも参照できます。
このときsymbol要素とは違い、参照元になっているg要素のオブジェクトも表示されます。

SVG1.1 グループ化について(g, symbol, defs) - はしくれエンジニアもどきのメモ

SVG内で使われている属性

今度は属性に着目します。

こちらにいろいろ載っていました。

ASCII.jp:SVGの見た目は「属性」で書く――HTMLとの違い

SVGの属性の多くは、塗り色、線の色、線の太さといった図形の「見た目」を定義します。文書マークアップ言語であるHTMLでは、文脈に関係のない見た目はCSSで定義することが推奨されています。一方でSVGは、グラフィックのマークアップ言語なので、属性で見た目を定義することは一般的です。

このように書かれているので、SVGのコードの中で塗りつぶしの色や線の色、図形の変形なども属性で指定して良さそうです。
気兼ねなく使えます。

そして、図形の色や見た目に関わる属性はプレゼンテーション属性といい、CSSのプロパティでも同じく指定できる属性を指します。

data-name属性

SVGというよりもHTML5の知識です。

いまさら聞けない、HTML5カスタムデータ属性の基本と使いどころ – WPJ

これはカスタムデータ属性です。 スタイルではなくデータを格納するための属性です。

書き出し時に善意で付けてくれている場合がありますが、必要なければ削除しましょう。

transform属性

path要素やg要素にありました。

CSSのtransformプロパティと同じく、座標や角度を指定して移動・拡大縮小・回転を設定する属性です。

transform はプレゼンテーション属性です。
書き出すとたまに transform が指定されているときがありますが(特にtranslate)、
こちらも不必要に入っているときは図形の座標など値を修正してtransform を削除していきたいところです。

アウトライン(線)に関する属性

イラレで指定できるかのように、アウトラインに関する属性は結構あります。

SVGでアウトラインをカスタマイズしてみよう | Webクリエイターボックス

属性 内容
stroke アウトラインの色
stroke-width アウトラインの幅。単位なしでは座標に従う
stroke-opacity アウトラインの透明度
stroke-linecap アウトラインの端の形。 butt(デフォルト、端で切れる)、round(丸く)、square(四角く)
stroke-linejoin アウトラインの 角の形。miter(デフォルト、尖っている)、round(丸く)、bevel(尖りが切られている)
stroke-dasharray アウトラインの破線のパターン。線分、隙間、線分、隙間...と繰り返しのパターンを指定します(値が複数の場合があるので「array」なのですね)
stroke-dashoffset アウトラインの破線の開始位置

stroke-dasharrayの指定、フォトショやイラレでも未だ理屈がよく分かっていなかったのですが、分かった気がしました。

そしてstroke-dasharray と一緒に使うことが多い stroke-dashoffset を使って一筆書きのアニメーションも作れるのですね!(憧れ)

styleの優先度

ある図形に色を付ける場合、以下の方法があります。

  • インラインの属性:<path fill="red" />
  • インラインのstyle属性:<path style="fill: yellow" />
  • style要素:<style>.style-element { fill: blue; }</style>
  • 別ファイルのCSS.css-file { fill: green; }

優先度の順番を調べてみました。

HTML

<svg width="200" height="200" viewBox="0, 0, 200, 200" xmlns="http://www.w3.org/2000/svg">
  <style>
    .style-element {fill: blue;}  <!-- blue②番目 -->
  </style>
  <circle class="style-element css-file" style="fill: yellow" fill="red" cx="100" cy="100" r="90" />  <!-- yellow①番目、red④番目 -->
</svg>

CSS

.css-file {
  fill: green;  // green③番目
}

よって、

  1. インラインのstyle属性:<path style="fill: yellow" />
  2. style要素:<style>.style-element { fill: blue; }</style>
  3. 別ファイルのCSS.css-file { fill: green; }
  4. インラインの属性:<path fill="red" />

このような順番で適用されていました。

1〜3はCSSの優先度そのままです。
プレゼンテーション属性(覚えたて)を使ったスタイルは優先度が低いことを覚えておこうと思います。

その他 - 波のアニメーション

波が動くアニメーションを実装する際にこちらを参考にしました。

SVGとanimationで滑らかな動きの波っぽいやつ | かちびと.net

波の1サイクルの部分の図形を左から右へ、繰り返し動かしていると波に見えます。

作りたかったのは小さな波だったので、background-repeatで波のSVG画像を表示させてアニメーションさせました。
(これならpng画像でやっても同じですね)

複数の波を重ねる場合は、 use を使うといいですね。
SVG側では波の位置・色、cssでは use に動きの指定をしています。

このあたりの指定の仕方は個人の好みやアニメーションの仕様にもよるのかと思います。

また波のアニメーションの中でこちらも見ました。

[CSS]テキストリンクのホバー時に、波線をうにょうにょとアニメーションさせるスタイルシートのテクニック | コリス

テキストhover時に波アニメーション付きのSVG画像を背景画像にしています。

波の動かし方は1つ目の参考ページと同じなのですが、
驚く点は、CSSファイルのインラインでSVG画像を指定しているところです。
background-image: url("data:image/svg+xml;... と入っています。

他の拡張子の画像ではできない、SVGならではの力技に見えました。

さいごに

アイコンとして使うことが増えてきたSVGですが、アイコンやイラストなど、いろいろなもののアニメーションを探していきたいです。

さっそく別の記事で練習してみました。

記事を通して基本的な部分は分かってきました。よかった...!

おしまい。

SVGで図形を書いてみよう

練習にイチからSVGを書いてみようと思いました。

use を使った物を作りたいので、同じ図形が複数出てくるもの...五輪!

今にぴったりなので作ってみることにしました。

著作権の問題でNGでした

もしやと思って調べたところ、

オリンピック・パラリンピックに関する知的財産等の無断使用および不正使用ないし流用は法的にも罰せられます。

オリンピック・パラリンピックに関するエンブレム、ロゴ、用語、名称をはじめとする知的財産は、日本国内では「商標法」、「不正競争防止法」、「著作権法」等により保護されています。

フリー素材でロゴ画像が手に入るぐらいなので大丈夫そうですが、こわいのでやめておきます。
(すでに登録していた五輪画像が記事のサムネイルになってしまっている。どうしたらいいのでしょう)

他の画像を探さなくては。

目標の画像

オリンピック開催中に作ろうという期限もなくなったので、せっかくならいろいろな要素を盛り込んだものを作りたいと思い、下記イラレで作った画像を目指します。

f:id:yokoyoko_115:20210807012044j:plain

The 子供の絵!
子供の絵にはお日様が右上か左上によく描かれていますよね。太陽の存在感はすごい。

ポイントは

  • 虹:グラデーションをかける
  • 雲:useを使う、拡大縮小
  • 太陽:光の線でuseを使う

です。

あわよくば太陽の光の線が回転して、雲が右から左に動かせたらいいなと思います。

作ってみました

動かす前と動かしたあとでコードが若干変わりました。以下は動かしています。

See the Pen GORIN by yokoyoko (@yokoyoko_code) on CodePen.

コードについて

今回は虹・雲・太陽をそれぞれのSVGの図形として、それらをまとめて1つのSVG画像としています。

1つずつコードを整えていきます。

イラレでうまく作れず、ドーナツの半分の形にしたところ、グラデーションが崩れてしまいました。
パスだけ使って、グラデーションはsvgファイルを直接触ろうと思います。

イラレから書き出した虹SVG

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 380.46 190">
  <defs>
    <style>.cls-1{fill:url(#名称未設定グラデーション_350);}</style>
    <radialGradient id="名称未設定グラデーション_350" cx="190.23" cy="95" r="150.35" gradientUnits="userSpaceOnUse">
      <stop offset="0.7" stop-color="#c7659a"/>
      <stop offset="0.76" stop-color="#51527d"/>
      <stop offset="0.82" stop-color="#4facd2"/>
      <stop offset="0.88" stop-color="#369263"/>
      <stop offset="0.94" stop-color="#f4eb5f"/>
      <stop offset="1" stop-color="#d94f4f"/>
    </radialGradient>
  </defs>
  <g id="レイヤー_2" data-name="レイヤー 2">
    <g id="レイヤー_1-2" data-name="レイヤー 1">
    <path class="cls-1" d="M55.69,190a134.54,134.54,0,0,1,269.08,0h55.69C380.33,85,295.21,0,190.23,0S.13,85,0,190Z"/>
    </g>
  </g>
</svg>

path が虹の輪郭で、style で塗りつぶし(fill)をしています。
fill プロパティでurlを radialGradient に紐づけています。
そして radialGradientdefs で囲まれて定義され、見えなくなっています。

fill属性を使う

fill はプロパティと属性、どちらもあります。

この場合 style を使って path にfillプロパティをあてていますが、直接 path にfill属性を入れてもできます。

<path fill="url(#rainbow)" d="M55.69,190a134.54,134.54,0,0,1,269.08,0h55.69C380.33,85,295.21,0,190.23,0S.13,85,0,190Z"/>

これで style は削除できます。

radialGradient要素の設定

radialGradient は放射状のグラデーションを定義します。 cx、cy属性は、viewBoxのx、y座標を示し、r属性は半径を示します。

半円ドーナツ図形の中心がグラデーションの起点になってしまっていたので、xの座標は中央のままで、yの座標を図形の下辺にし、半径も半円ドーナツ図形をぴったり囲むように大きくします。

f:id:yokoyoko_115:20210808003235j:plain

ちなみに gradientUnits="userSpaceOnUse" についても調べましたが、分かりませんでした。

グラデーションとパターン – SVG 1.1 (第2版)

gradientUnits="userSpaceOnUse" の場合、 cx, cy, r, fx, fy は,グラデーション要素が参照された所における現在の利用座標系(すなわち fill あるいは stroke プロパティによりグラデーション要素を参照している要素の利用座標系)に対し, gradientTransform 属性で指定される変換を適用した結果の座標系における値を表す。

これを書かない、または gradientUnits="objectBoundingBox" (初期値)だと、虹が真っ赤になってしまったので今回の場合は必要です。

gradientTransform属性を知る時が来れば分かるようになるかもしれません。

イラレから書き出した雲SVG

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 121 80.67">
  <defs>
    <style>.cls-1{fill:#fff;}</style>
  </defs>
  <g id="レイヤー_2" data-name="レイヤー 2">
    <g id="レイヤー_1-2" data-name="レイヤー 1">
      <path class="cls-1" d="M96.51,15.85a24.73,24.73,0,0,0-4.33.4A24.45,24.45,0,0,0,51.61,7.41a24.4,24.4,0,0,0-38.27,24.1A24.48,24.48,0,1,0,44.5,67.38a24.47,24.47,0,0,0,45-3.58,24.48,24.48,0,1,0,7-47.95Z"/>
    </g>
  </g>
</svg>

雲は2つ出てくるのでuseを使ってみます。

viewBox,width,heightの値で雲の大きさ、x,yの値で雲の表示位置を設定する

1つのSVG画像として表示するにあたり、虹や雲など個別で書き出したSVG図形をどう配置するか混乱しましたが、MDNのページを見てこう考えました。

<symbol> - SVG: Scalable Vector Graphics | MDN

  1. まず定義側のviewBoxを考える。すでに書き出されたpathの座標(d)があるのでここはむやみに書き換えられない。viewBoxは図形いっぱいの枠と考える
  2. 定義側のwidth、heightを考える。これらの値に図形が拡大縮小する。そしてこのwidth、heightの値が呼び出し側の単位と合う
  3. 呼び出し側の use にwidth、height属性で大きさ、x、y属性で座標を指定して配置する

図にするとこういうイメージです。

f:id:yokoyoko_115:20210809014420j:plain

もし symbol にviewBox属性を指定していないと、たとえ symbol にwidth、height属性を指定していても use でwidth、height属性を指定して拡大・縮小できませんでした。もとの大きさのままです。
symbol にviewBox属性は必須

もしsymbol にwidth、height属性を指定していない場合、use でwidth、height属性を指定していれば拡大・縮小できます。
このとき use でwidth、height属性を指定していなければ呼び出し側のviewBoxの大きさいっぱいになります。
symbol または use でwidth、height属性を指定する

<use> - SVG: Scalable Vector Graphics | MDN

use 要素にあるx, y, width (en-US), height (en-US), href の各属性のみが参照される要素に設定されているものを上書きします。

複数の図形を拡大・縮小させて配置するなら use でwidth、height属性を指定することになりそうです。

ちなみに、widthかheight片方のみを指定した場合(もう片方が未指定)、widthかheightの初期値は呼び出し側のviewBoxの大きさになりますが、おそらくpreserveAspectRatioの値がデフォルトであることが原因で、呼び出した図形はviewBoxの範囲内で上下左右中央に配置されます。

x、y属性でうまく配置するには、width、height属性で図形ぴったりの比率で指定する必要がありそうです。

先ほどの虹も結局 use を使うことになりました。

太陽

最後は太陽です。

イラレから書き出した太陽SVG

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 115.39 115.39">
  <defs>
    <style>.cls-1{fill:#f08218;}.cls-2{fill:none;stroke:#f08218;stroke-miterlimit:10;stroke-width:4px;}</style>
  </defs>
  <g id="レイヤー_2" data-name="レイヤー 2">
    <g id="レイヤー_1-2" data-name="レイヤー 1">
      <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/>
      <line class="cls-2" x1="15.94" y1="57.7" y2="57.7"/>
      <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/>
      <line class="cls-2" x1="28.17" y1="87.22" x2="16.9" y2="98.49"/>
      <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/>
      <line class="cls-2" x1="57.7" y1="99.45" x2="57.7" y2="115.39"/>
      <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/>
      <line class="cls-2" x1="87.22" y1="87.22" x2="98.49" y2="98.49"/>
      <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/>
      <line class="cls-2" x1="99.45" y1="57.7" x2="115.39" y2="57.7"/>
      <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/>
      <line class="cls-2" x1="87.22" y1="28.17" x2="98.49" y2="16.9"/>
      <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/>
      <line class="cls-2" x1="57.7" y1="15.94" x2="57.7"/>
      <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/>
      <line class="cls-2" x1="28.17" y1="28.17" x2="16.9" y2="16.9"/>
    </g>
  </g>
</svg>

line は複数あるのは分かりますが何故か circle も同じ数だけあります!
各属性値も同じなので、8つの円が重なっているようです。7つは消します。

この line のx1、y1とx2、y2属性、1が始点で2が終点のx、y座標をあらわしています。
指定されていない場合、初期値は0です。

8つある line のうち1つを使って、太陽の縁を中心に45度ずつ回転させて表示させてみよう!

...と当初は思ったのですが、use で太陽の光の線を8回呼び出すのはどうなのか、それだと後々の「光の線を回転させる」が難しいのではと思い、まとめて呼び出すことにしました。

symbolを呼び出すのかgを呼び出すのか

太陽の図形は「円」と「光の線」で分けて出したいので、ざっくりと下記のようにしました。

定義側の太陽

    <symbol>
      <g>
        <circle>
      </g>
      <g>
        <line>
           :  (✕ 7)
      </g>
    </symbol> 

symbolg はid属性を振れば use で呼び出せます。
この場合は太陽まるごと呼び出すときは symbol 、「円」と「光の線」で2回呼び出すときは g を使ってできます。

2つは呼び出したときのhtmlへの出力が異なります。

symbol の場合は、symbolsvg に置き換わった形で出力されます。 中で g に「円」と「光の線」がそれぞれ囲まれています。 一方 g の場合はそのまま出力されます。

なので「円」と「光の線」で分けられていながら、1つの図形としてまとまっている symbol で呼び出すことにしました。

2つのg要素をさらにg要素で囲い、それを呼び出す方法もありますが、g要素は呼び出し側でwidthとheightの指定ができません。

symbolg の使い分けは、いろいろ作っていくうちに慣れてきそうな気がしました。
迷ったら symbol で囲います。

classとスタイルのあて方

pathguse など様々な要素でスタイルをあてることができるので、何がベストなのか大混乱です。

大事なのは定義側で指定したスタイルを呼び出し側で上書きできないということです。

雲のところで大きさの上書き指定は可能だがその他はできない、というのはこの事だったのですね!

定義側の pathfill: red とした場合、呼び出し側の usefill: blue と指定しても青にはなりません。

そう考えると、呼び出し側に統一してスタイルをあてた方が良いのでしょうか。
先の「symbolを呼び出すのかgを呼び出すのか」問題とも関係してくるので、状況によりけりなのかなと思いました。

ちなみにデベロッパーツールでスタイルを確認したときにつまづきました。定義側と呼び出し側、それぞれを確認しないといけないです。

定義側のsvgのスペースを削除する

SVG画像の前に、定義したsvg要素に謎の300x150pxの隙間ができました。

svgdisplay="none" を書いたり、csssvg { display: none; } を指定するとうまくいくと思ったのですが、
なんと虹が消えてしまいました。

虹で使っている radialGradientdisplay: none で無効になってしまったのだと思います。

そこで色々と試行錯誤したところ、

/* 定義側のsvg */
.svg-defs {
  display: block;
  width: 0;
  height: 0;
}

としたところ、隙間が消えてくれました。

他のケースでも使えるといいなと思います。

できあがったSVG(動かない)

これで虹・雲・太陽がそろいました!
大きさや位置が目分量ですが真似できました◎

HTML

<!-- 定義側 -->
<svg class="svg-defs">
  <defs>
    <!-- 虹 -->
    <symbol id="rainbow" viewBox="0 0 380.46 190">
      <radialGradient id="rainbow-gra" cx="190.23" cy="190" r="190.23" gradientUnits="userSpaceOnUse">
        <stop offset="0.7" stop-color="#c7659a"/>
        <stop offset="0.76" stop-color="#51527d"/>
        <stop offset="0.82" stop-color="#4facd2"/>
        <stop offset="0.88" stop-color="#369263"/>
        <stop offset="0.94" stop-color="#f4eb5f"/>
        <stop offset="1" stop-color="#d94f4f"/>
      </radialGradient>
      <path fill="url(#rainbow-gra)" d="M55.69,190a134.54,134.54,0,0,1,269.08,0h55.69C380.33,85,295.21,0,190.23,0S.13,85,0,190Z"/>
    </symbol>
    <!-- 雲 -->
    <symbol id="cloud" viewBox="0 0 121 80.67">
      <path d="M96.51,15.85a24.73,24.73,0,0,0-4.33.4A24.45,24.45,0,0,0,51.61,7.41a24.4,24.4,0,0,0-38.27,24.1A24.48,24.48,0,1,0,44.5,67.38a24.47,24.47,0,0,0,45-3.58,24.48,24.48,0,1,0,7-47.95Z"/>
    </symbol>
    <!-- 太陽 -->
    <symbol id="sun" viewBox="0 0 115.39 115.39">
      <g id="sun-circle">
        <circle class="sun-circle" cx="57.7" cy="57.7" r="30.28"/>
      </g>
      <g id="sun-line" class="sun-line">
        <line x1="15.94" y1="57.7" y2="57.7"/>
        <line x1="28.17" y1="87.22" x2="16.9" y2="98.49"/>
        <line x1="57.7" y1="99.45" x2="57.7" y2="115.39"/>
        <line x1="87.22" y1="87.22" x2="98.49" y2="98.49"/>
        <line x1="99.45" y1="57.7" x2="115.39" y2="57.7"/>
        <line x1="87.22" y1="28.17" x2="98.49" y2="16.9"/>
        <line x1="57.7" y1="15.94" x2="57.7"/>
        <line x1="28.17" y1="28.17" x2="16.9" y2="16.9"/>
      </g>
    </symbol>    
  </defs>
</svg>

<!-- 呼び出し側 -->
<svg  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="oekaki-area" width="500" height="300" viewBox="0 0 500 300" role="img" aria-labelledby="oekaki">
<!-- 雲 -->
  <use href="#cloud" class="cloud" x="250" y="10" width="140" height="93.3"></use>
  <use href="#cloud" class="cloud" x="350" y="160" width="120" height="80"></use>
<!-- 虹 -->
  <use href="#rainbow" x="75" y="125"  width="350" height="175"></use>
<!-- 太陽 -->
  <use href="#sun" x="20" y="20" width="130" height="130"></use> 
<!-- 太陽のパーツを個別に出す場合 -->
<!--   <use class="sun-circle" href="#sun-circle" x="20" y="20" width="130" height="130"></use> 
  <use class="sun-line" href="#sun-line" x="20" y="20" width="130" height="130"></use>
</svg> -->

CSS

.svg-defs {
  display: block;
  width: 0;
  height: 0;
}

#oekaki-area {
  border: 1px solid #000;
  background-color: #def0f3;
}
.cloud {
  fill: #fff;
}
.sun-circle {
  fill: #f08218;
}
.sun-line {
  stroke: #f08218;
  stroke-miterlimit: 10;
  stroke-width: 4px;
}

雲を動かす

さらにSVGの図形に動きを加えてみます。

雲は静止画の位置から左へ移動して、また右から現れるようにします。

こちらを見ながら書いたらばっちりだと思います。

SVG アニメーション(SMIL を使ったアニメーション) / Web Design Leaves

せっかくならSVGの中で動かしたい...!

SMIL(Synchronized Multimedia Integration Language)というマークアップ言語を使う方法がそれにあたるようです。

SVG animation with SMIL - SVG: Scalable Vector Graphics | MDN

SMIL では以下のようなことができます: ・要素の数値属性 (x, y など) のアニメーション ・トランスフォーム属性 (translation または rotation) のアニメーション ・色属性のアニメーション ・モーションパスに従う

具体的には animateMotionanimate など、アニメーションに関係するSVG要素を書きます。
cssを書かずにアニメーションできます。

animateMotion、animate要素が使えるかどうか

はじめは animateMotion を使うのが適当と思いましたが、pathに沿った動きをさせたい場合に使うようです。

ふわふわ上下しながら流れていって欲しいのが正直なところですが、ここは右から左に水平に動かします。

続いて animate を考えましたが、こちらは要素の属性のアニメーションに使うものなので、雲の path には位置に関わる属性(x,y)がなくて動かせない。
雲を変形させることはできます。

cx,cy属性のある circle であれば animate を使って移動のアニメーションができます。

呼び出し側で使えない。定義側も問題あり

animate などのアニメーション要素は use と絡めて書くことはできないのではと思いました。

アニメーション要素は、 path など形を表す要素の子要素になるように書きます。
<path><animate></path> です。
path で囲む書き方もできるのですね。

use を使うと svg で書き出されるので、animate を差し込む余地がありません。

また、定義側でアニメーションの定義をすればよいのではと考えましたが、雲の定義の仕方では無理そうです。 定義側では雲の形状だけを設定していて、viewBoxが雲いっぱいのサイズのため雲が移動するスペースがありません。

呼び出し側と同じviewBoxで、定義側で雲が配置されていれば可能なのかもしれませんが、まだ今の知識では書けません。

となると、雲の場合はcssに頼るしかないのかもしれません。

cssのanimationプロパティで動かす

静止画の位置から右から左に移動させる、というところで少しつまずきました。

0%→100%で右端(見えない)→左端(見えない)とすると、最初の表示のタイミングでは雲が見えません。

その場合は animation-delay プロパティにマイナスの値を設定するとうまくいきます。
遅らすのではなく早めることができるので、0%→100%の途中からスタートできます。

10秒かけた移動だから...とこれも目分量で設定しました。

雲は動きました!

太陽の光を動かす

こちらはSMIL で動かすことができました。

animateTransform要素で回転させる

参考ページなどを見てすんなりとできました。

g で囲った中で animateTransform を書いて g 内の要素の動きを設定できます。

ちなみにデフォルトでは回転は時計回り(右回り)ですが、to="-360, ..." とマイナスの値だと左回りになりました。

さいごに

当初の五輪だったらここまで長くならなかったですね。
SVGはまだまだ奥深いことがわかりました。

※検証段階の内容が多分に含まれているので、分かり次第修正していきます。

順番が前後してしまいますが、この後SVGについてまとめた記事を書きます。

実践が大事!おしまい!

PHPでTODOリストを作ってみた(2) - TODOの追加

TODOリストを作ってみています。
ピンポイントの「TODOリストの作り方」記事は見ずに、できるかぎり調べて書いています。

前回はDBのテーブルの表示までできました。

次は「登録」ボタンを押してPOST送信したデータをテーブルに追加します。

4. SQL文でレコードの追加

いきなりどうすればいいのか分からないので調べました。

formタグから入力したデータをデータベースに登録 | CBC | Webデザインやプログラミングの基礎学習

【プログラミング構築】入力したデータをPOST送信でデータベースに登録しよう | 株式会社LIG

1つ目の記事を見ると、接続部分と追加部分、それぞれでtry/catch で囲っています。
2つ目の記事では追加部分はそのまま書かれています(クラスのメソッドになっている...複雑)。

ひとまずtry/catch で囲わずに書いてみます。
一旦一覧表示は非表示にします。

SQL INSERT文

INSERT文もすっかり忘れました。

INSERT文(SQLを基本から学ぶシリーズ)

INSERT INTO テーブル名 (列名1, 列名2,...) VALUES (値1, 値2,...);

こちらに倣って書こうと思いますが、VALUES の中は「パラメータをつかってバインドしている」ようです。
VALUES ($POST_['day-r'], $POST_['priority'],...) と、直接VALUES の中に POST送信のデータを入れるのは良くないということなのでしょう。

ここはまず固定値を入れてINSERTしてみます。

サーバーエラーになる

POST送信のタイミングでNSERTしてみましたが、500のエラーが返ってきました。

エラーになったコードです。

//TODO追加
if (!empty($_POST['content'])) {
  $sql  = 'INSERT INTO todo(id, registration-date, priority, content, note, period-date, category) VALUES(NULL, 2021-06-01, "中", "病院に行く", "10時に予約", 2021-06-05, "外出")';
  $stmt = $pdo->query($sql);
  $stmt->execute();
}

そこでtry/catchを入れてみます。

すると以下のエラーが返ってきました。

Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '-date, priority, content, note, period-date, category) VALUES(NULL, 2021-06-01, ' at line 1

貴重な手がかり!
どうやらSQLの構文ミスのようです。

「'id, registration-date...'付近」ではなく「'-date, ..' 付近」の構文を確認してください、というのが引っかかります。
そもそもハイフン -カラム名(フィールド名)に使えるのかどうか調べました。

データベース名にハイフン(-)入れたらSQL文でエラーになった 【特殊文字】 - Qiita

DB名やカラム名では極力ハイフンを使わない方がよいですね。

ハイフンを使う場合はバッククオート「`」で囲むエスケープ処理が必要だそうです。
また「table」などの予約語を使う場合もエスケープ処理が必要です。

なので、上記のSQL文はregistration-dateperiod-date をバッククオートで囲みました。

すると、無事にDBに追加されていました!
(ページ読み込みを何回かしたためか、たくさん入っている)

プレースホルダでバインドする

VALUEに固定値を入れていたので、パラメータを使ってPOSTデータをINSERTします。

バインドとは

バインド (bind)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
https://wa3.i-3-i.info/word12448.html

バインドとは関連付ける紐付けるという意味です。

先程のLIGさんの記事では

バインドとは、上記のようにプレースホルダに変数を割り当てる処理のことをいいます。

また、今回のようにPOSTデータを受け取り、データベースに登録を行う場合に、insert文に直接取得した変数を与える形だと「SQLインジェクション」というデータベースの不正操作を引き起こすことになってしまいますが、PDOを用いてプレースホルダを利用して取得変数を組み込んだり、バインドする際に型を指定することでSQLインジェクション対策を含むことが容易にできます。

とあります。

具体的には

$stmt->bindParam(':name', $_POST['name'], PDO::PARAM_STR);

ですね。

プレースホルダが「:name」で変数が「$_POST['name']」ですね。

プレースホルダとは

プレースホルダ「:で始まる代替文字列」の部分です。

引き続きLIGさんの記事では

プレースホルダとは実際の内容を後から挿入するために仮で確保している場所

とあります。

プレースホルダとは何か?SQLインジェクション攻撃を回避せよ!

こちらでもSQLインジェクションについて具体的に説明されています。

SQL文の中で「変動する箇所」には必ずプレースホルダを使いましょう。

とあるので、以下の書き方を必ずするということですね。

バインドパラメータ、バインド変数は文字通りバインドした変数ですね。多分。
記事をいろいろ見ていると表現もいろいろで、考えると混乱しますが、きっとそういう解釈でいいはずです。

データを入れる前の事前準備のprepareメソッドを使って以下のようにしました。

☆idはオートインクリメントなので、飛ばしました。

  if (!empty($_POST['content'])) {
    $sql  = 'INSERT INTO todo(`registration-date`, priority, content, note, `period-date`, category) VALUES(:registration_date, :priority, :content, :note, :period_date, :category)';
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(':registration_date', $_POST['day-r'], PDO::PARAM_STR);
    $stmt->bindParam(':priority', $_POST['priority'], PDO::PARAM_STR);
    $stmt->bindParam(':content', $_POST['content'], PDO::PARAM_STR);
    $stmt->bindParam(':note', $_POST['note'], PDO::PARAM_STR);
    $stmt->bindParam(':period_date', $_POST['day-p'], PDO::PARAM_STR);
    $stmt->bindParam(':category', $_POST['category'], PDO::PARAM_STR);
    $stmt->execute();
  }

登録時に日付のフォーマット(yyyy-mm-dd ←正式な書き方ではないかも)に気をつけて入力、送信したら無事にINSERTできました◎

5. TODO登録後の一覧表示

一覧表示は以前にできていたので、今度は新しく登録したtodoを含めてtodoリストを表示します。

DOCTYPEタグの前にいろいろとPHPで処理を書いていますが、try/catch の中にまとめて入れてみました。

try {
  // DBに接続
  $dsn = 'mysql:dbname=practice;host=localhost;charset=utf8mb4';
  $username = 'root';
  $password = 'root';
  $driver_options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
  ];
  $pdo = new PDO($dsn, $username, $password, $driver_options);

  //TODO追加
  if (!empty($_POST['content'])) {
    $sql  = 'INSERT INTO todo(`registration-date`, priority, content, note, `period-date`, category) VALUES(:registration_date, :priority, :content, :note, :period_date, :category)';
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(':registration_date', $_POST['day-r'], PDO::PARAM_STR);
    $stmt->bindParam(':priority', $_POST['priority'], PDO::PARAM_STR);
    $stmt->bindParam(':content', $_POST['content'], PDO::PARAM_STR);
    $stmt->bindParam(':note', $_POST['note'], PDO::PARAM_STR);
    $stmt->bindParam(':period_date', $_POST['day-p'], PDO::PARAM_STR);
    $stmt->bindParam(':category', $_POST['category'], PDO::PARAM_STR);
    $stmt->execute();
  }

  // テーブル情報取得
  $stmt = $pdo->query('SELECT * FROM todo');
  $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

} catch {
  header('Content-Type: text/plain; charset=UTF-8', true, 404);
  exit($e->getMessage());
}

これでうまく動きました!

同じ $stmt を使い回していいものなのか、catch の中身も果たして妥当なのか(エラーはログに書き出すはず)まだ怪しいですがひとまず。

次回へ続けたい...

追加ときたら削除まではやっておきたいです。

ただ削除ボタンは作らないといけないので、ボタンを付けたら次へ進もうと思います。

答え合わせまではやりたいです。

PHPでTODOリストを作ってみた(1) - テーブルの表示

TODOリストを作ってみます。

作り方を調べたら真似をして終わりになってしまうので、調べることは最低限にして試行錯誤していこうと思います。

☆以下は必要に応じて追記していきます。

環境

  • MAMP(ローカル環境)
  • PHP(7.4.9)
  • MySQL(5.7.30)

仕様

普段TODOリストのツールを使っていないので(手書き。アナログ)、こんなものがあればいいなで考えました。

  • 登録した日付、重要度、内容、備考、期限 カテゴリ の項目がある
  • TODOの作成、削除、並び替え、絞り込みができる

必要なもの(ファイル、ソフトなど)

  • DB(MySQL
  • phpMyAdmin(4.9.5)
  • フォーム画面(form.php)・・・入力・確認・完了画面も1つのファイル

作り方の手順

まずは最短でTODOの登録を考えます(表側にこだわりすぎるとそこで終わってしまいそうなので)。

  1. フォーム画面を作る(html)
    この時点では日付を除いてテキストのフォーム
    POST送信
  2. DBにテーブルを作る
    PHPMyAdmin
    適当に2、3件直接登録しておく
  3. フォーム画面に一覧(テーブル)の表示
    DBへの接続
    SQL文でデータを取得してphpで繰り返し処理
  4. SQL文でレコードの追加
    「登録」ボタンを押してPOST送信したデータを追加する
  5. TODO登録後の一覧表示
    新しく登録したTODOも表示する

果たして最後までできるかどうか。

TODOの登録までできてから、削除や並び替えなどをやるか考えます。

1. フォーム画面を作る(html)

form関連はあまり書く機会も少ないので早速調べました。

【HTML入門】formタグを使ってフォームを作る方法を1から解説 | 侍エンジニアブログ

見た目は想像通りです!

form.php

せめて隙間ぐらい空ければよかったかなと思いましたが、まぁいいです。

POST送信の確認

formタグを method="post" とするとPOST送信になります。

また action 属性にデータの送信先を指定します。
フォーム画面であるこの同一ファイルに送ることもできます。
この場合 action="/form.php"(ルート相対パスaction="form.php相対パス) でも form.phpに送ることができました。

ちゃんと届いているかどうか、form.phpに出力してみました。

<?php if ($_POST['content']) : ?>
  <h2>登録したタスク</h2>
  <p><?= $_POST['content'] ?></p>
<?php endif; ?>

TODO(content)入力後登録ボタンを押すとタスク内容が無事表示されました。

2. DBにテーブルを作る

探り探りの部分へ突入です。

おそらくあらかじめテーブルを作っておく必要があると思います。

コマンドラインは習得に時間がかかるのでMAMPに入っているPHPMyAdminを使います。
コマンドラインでの作り方がたくさん記事にあったので若干後悔)

①データベースを作る

PHPMyAdminにて。
「Create database」でデータベース名を付けて新規で作ります。
今後も使うかと思い「practice」と付けました(複数テーブル使う練習するかもしれませんが、今のところ)。

②テーブルを作る

①で作ったデータベースの中に「todo」というテーブルを作ります。 そのときにカラムの数も設定するので、登録日、重要度、内容...と数えて6と入れました。

f:id:yokoyoko_115:20210522013659p:plain

そしてカラムの設定をしました。

一通り入れて「Preview SQL」ボタンを押すと、SQL文が確認できました。

CREATE TABLE `practice`.`todo` ( `registration-date` DATE NOT NULL , `priority` TEXT NOT NULL , `content` TEXT NOT NULL , `note` TEXT NOT NULL , `period-date` DATE NOT NULL , `category` TEXT NOT NULL ) ENGINE = InnoDB;

何となく読めはするけど書けないです。

「Save」ボタンを押すと、テーブルが作れました。

f:id:yokoyoko_115:20210522014019p:plain

テーブル作成において触ったのはNameとTypeだけです。
すべてTypeはTEXTにしようかとも思いましたが、せっかく選べるので日付のTypeはDATEにしました。
重要度やカテゴリーも本当は数字(INT)にして、別テーブルでカテゴリーIDとカテゴリー名の対応表を用意して紐付けるのがベストなのだろうと思いましたが、いきなりそれはできないのでTEXTです。

インデックスを作る

テーブルの表示の下に

Indexes
No index defined!

とありました。その下には
Create an index on [  ] columns

と、インデックスを作るところがありました。

インデックスとは?

インデックスの意味とメリット・デメリット | SQLite入門

インデックスを簡単に言うと対象のカラムのデータを取り出し、高速に検索できるように手を加えて保存しておいたものです。

インデックスを作成することでテーブルとは別に検索用に最適化された状態で必要なデータだけがテーブルとは別に保存されるということを覚えておいて下さい。

「対象のカラム」(記事の中ではname、人の名前)はアルファベットや数値が昇順などで並び替えられているので、それを保存しておくことで検索に役立てるみたいです。

この場合nameで検索するときは早くなるかもしれませんが、別のカラムで検索する場合にはインデックスは効果があるのでしょうか。
インデックスには複数カラム、さらには複数のインデックスが登録できるようです。
検索に使うカラムは登録した方がいいのでしょうか。

今回の場合、重要度、期日、カテゴリで検索することになりそうです。
全カラムの半分近くがインデックスって、果たして妥当なのかどうか。

インデックスを作るかどうか書かれています。

MySQLのIndexをはるコツ - Qiita

SQLの理解が何となくだと厳しいです。

ひとまず必要そうなidカラムの追加と、インデックスの作成(idと期日、period-date)をします。

色々いじったらこうなりました。

f:id:yokoyoko_115:20210522235806p:plain

idだけUNIQUEにしたかったのですが、これだと2つともUNIQUEになっていますね。
さらにテーブルにはidとperiod-date両方にうっすら鍵マークが付きました。

インデックスはidで検索することはないので期日だけで良かったのかもしれません。

プライマリキー:id
インデックス:period-date(Index choice:INDEX ←Index choiceとは?)

に修正します。

最終的にはこうなりました。

f:id:yokoyoko_115:20210523005602p:plain

idの「AUTO_INCREMENT」はオートインクリメント。自動で連番を振ってくれるものです。
設定画面で「A_I」と書かれていて、はじめ何のことだか分かりませんでした。

何となくの設定でも、今回のような簡単なものなら問題はなさそうです。勘です。
まだまだ理解が微妙です。

③レコードを登録する

PHPMyAdminから適当にTODOを入れてみます。

今まで見ていた画面はStructiure(構造)タブで、レコードの登録はInsert(挿入)タブです。

登録後にSQL文が表示されます。

INSERT INTO `todo` (`id`, `registration-date`, `priority`, `content`, `note`, `period-date`, `category`) VALUES (NULL, '2021-05-23', '中', '部屋の掃除', '朝やる', '2021-05-28', '家事');

3つ入れました。idが飛び飛びになっているのは、誤って入れてしまったレコードを削除したためです。さっそく気持ち悪い。

f:id:yokoyoko_115:20210523011659p:plain

ここからがいよいよ問題です。

3. フォーム画面に一覧(テーブル)の表示

まずはDBに接続が必要!だと思うので調べました。

MySQLSQLとは

いきなりおさらいです。

MySQL

MySQLとは?初心者にわかりやすい説明

MySQLオープンソースリレーショナルデータベース管理システムです。

リレーショナルデータベース管理システムRDBMS:Relational DataBase Management System)です。
データベース管理システムも色々あるようです。

SQL (Structured Query Language)

SQLとは?基礎知識から具体例まで分かりやすく解説!

SQLはデータベース(RDBMS)を操作するための言語です。 SQLは国際標準化されているため、さまざまなデータベースで利用できます。有名なデータベースとしては、OracleMySQLPostgreSQLSQLiteなどが、いずれもSQLで操作可能です。

SQLは他のデータベースでも使われているので最低限は覚えたほうがいいですね。

PHPのデータベース接続の方法

記事を見ました。とても詳しいです。それぞれ2020年、2019年に更新されているので情報もさほど古くないのではないでしょうか(2021年5月現在)。

【PHP超入門】クラス~例外処理~PDOの基礎 - Qiita

PHPでデータベースに接続するときのまとめ - Qiita

データベースへの接続方法の一つにmysql関数を使った方法があります。

mysql関数を使った接続方法は、PHP 5.5.0 で非推奨になり、PHP 7.0.0 で削除されました。

mysql関数以外でデータベースに接続するには、PDOまたはMySQLiを使用します。

PDOをよく見るのでこちらを使う方法が主流のようです。

PDO(PHP Data Objects)とは

データベース抽象化レイヤの一つ
だそうです。

しっかり記事を読もうとすると奥が深く、心が折れそうなので、載っている方法で接続してみます!

$pdo = new PDO($dsn, $username, $password, $driver_options);

PDOクラスのインスタンスを生成してコンストラクタによって接続しています。

$pdo の中身が気になるので var_dump() してみました。

  // DBに接続
  $dsn = 'mysql:dbname=practice;host=localhost;charset=utf8mb4';
  $username = 'root';
  $password = '';
  $driver_options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
  ];
  $pdo = new PDO($dsn, $username, $password, $driver_options);

  var_dump($pdo);
  echo '<p>$pdoオブジェクトってvar_dumpできるの?</p>';

var_dumpもできないし、テキストもでませんでした。

接続部分でエラーが起こっているのかもしれません。
try,catchを使ってみることにします。

☆try/catch句についてはこちらに書きました。

  try {
    // DBに接続
    $dsn = 'mysql:dbname=practice;host=localhost;charset=utf8mb4';
    $username = 'root';
    $password = '';
    $driver_options = [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_EMULATE_PREPARES => false,
    ];
    $pdo = new PDO($dsn, $username, $password, $driver_options);

    var_dump($pdo);
    echo '<p>PDOクラスってvar_dumpできるの?</p>';
  } catch (PDOException $e) {
    exit($e->getMessage());
  }

すると

SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: NO)

と出てきました。エラーメッセージ便利!
パスワードが空なのがダメなようです。$password にrootを入れてみました。
すると

object(PDO)#1 (0) {} PDOクラスってvar_dumpできるの?

と出ました。接続はうまくいったみたいですね!

object(PDO)#1 (0) {}
データ型(クラス名) #id番号 (メンバ変数の個数、private protected public 全部) { メンバリスト}

だそうです。
#1 は`オブジェクトのIDのようです。

中身がさっぱり不明なこのPDOオブジェクトとは一体何なのでしょうか。
データベース抽象化レイヤがオブジェクトになったものなのでしょう。

header()について

参考時にコードで飛ばしていしまいましたが、レスポンス(?)時に送信するHTTPヘッダーの中身だと思われます。

header('Content-Type: text/plain; charset=UTF-8', true, 500);

DBに接続できないなどエラーになったときにレスポンスステータスコード500を返すように指定しています。

ヘッダー関連のやり取りがあるので、DB接続の記述はhtmlソースよりも前に記述しています(html要素よりも前)。

PDO::query、PDO::execメソッドとPDO::prepareメソッド

PDOオブジェクトからクエリを実行するのですが、使い分けをします。

ユーザー入力を伴わない、単にテーブルの一覧を表示させたいときは PDO::query を使います。

$result = $pdo->query(SQL文);

SQL文がMySQLに送られ実行され、結果が返ってきます。
返り値はPDOStatementクラスのオブジェクトで、$result に代入されます。

ステートメント、文...。

そしてqueryメソッドを使うときの注意点は

プリペアドステートメントを使わずにデータを取得するので、自分でエスケープ処理する必要があります。使うときは注意してください。

とのことです。意味がまだ分かりません。入力した値?SQL文?をエスケープするのでしょうか。

その下を読むと

ユーザーからの入力を伴うSQL文は、エスケープ処理する必要があります。 ユーザーからの入力を伴うSQL文のときは、プレースホルダを使いましょう。

エスケープする必要があるのはSQL文でした。

今回は不要ですね。

execメソッドとprepareメソッドを使う場合もおいおい調べます。

ひとまずPDOStatementクラスのオブジェクトをvar_dumpしてみます。

  $stmt = $pdo->query('SELECT * FROM todo');
  var_dump($stmt);

すると

object(PDOStatement)#2 (1) { ["queryString"]=> string(18) "SELECT * FROM todo" }

と出ました。

やはりPDOStatementはオブジェクトのようです。
SQL文がPDOStatementのメンバ変数として入っていますね。

PDOのオブジェクトといい、中身があっさりしすぎていて底が知れません。

このままではデータの表示ができないのでfetchメソッドで行または配列で取得するようにします。

$result = $stmt->fetch();
 var_dump($result);

すると、見事配列で表示されましたー!

array(14) {
  ["id"]=>
  int(1)
  [0]=>
  int(1)
  ["registration-date"]=>
  string(10) "2021-05-23"
  [1]=>
  string(10) "2021-05-23"
  ["priority"]=>
  string(3) "中"
  [2]=>
  string(3) "中"
  ["content"]=>
  string(15) "部屋の掃除"
  [3]=>
  string(15) "部屋の掃除"
  ["note"]=>
  string(9) "朝やる"
  [4]=>
  string(9) "朝やる"
  ["period-date"]=>
  string(10) "2021-05-28"
  [5]=>
  string(10) "2021-05-28"
  ["category"]=>
  string(6) "家事"
  [6]=>
  string(6) "家事"
}

ただこれでは1件取得なので、fetchAllメソッドを使って全件配列で取得します。
また、fetchAllメソッドの引数を PDO::FETCH_ASSOC にして、デフォルトであった0からの添字の配列を省きました。

  $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

配列を foreach して 以下のようになりました。

f:id:yokoyoko_115:20210530021706p:plain

次回へ続く...

TODOのDB登録と表示までやりたかったですが、長くなってしまったので一旦ここで区切ります。

cssも整えたい!
おしまい!

PHPで新しく知ったこと - try/catch、threw句、ルーティングなど

Hugoサイトのカスタマイズと同時進行でphpの勉強をしたいと思っています。

いよいよ手元にあるLaravelの本でサイトを作ろう(原点回帰)!としたところ、
それよりは基礎です!というお話を頂きました。

ごもっともです。

基本的な知識や一般的な関数・処理を知らなくては実際Laravelサイトを作っても身にならないので、ここは時間がかかっても基礎力を付けることを重視していきます。

ひとまず新しく知ったphpのことについてメモしていきます。

try/catch句とthrew句、Exceptionクラス

PHPで例外処理を使う方法【初心者向け】 | TechAcademyマガジン

PHP: Exception - Manual

こちらの記事のコードを参考にさせていただきました。

(PHP) try-catch文の使い方を解説 | hara-chan.com

function addNum($a,$b){
  try {
    // 処理
    // 例外が起こったとき、例外を投げる
    if ( is_numeric($a) == false) {
        throw new Exception('引数1が数字ではありません');
    }
    if ( is_numeric($b) == false) {
        throw new Exception ('引数2が数字ではありません');
    }
    return $a + $b;      
  } catch (Exception $e) {
    // 例外処理
    var_dump($e -> getMessage());    // 「引数1(または2)が数字ではありません」と出る
    return;
  }
}

意味は直訳で「try = 試みる」、「Exception = 例外」、「throw = 投げる」、「catch = 捕まえる」です。
投げて捕まえる、と言う表現はどこから来たのですかね。

それはさておき、
catchは例外が起こらないと処理されないことがわかりました。

catch句の最後に return があります。
改めてreturnを調べると、

関数を途中で終了する場合は、返り値を指定せずに任意の場所でreturnのみを記述することで制御が呼び出し元に戻ります。

という意味にあたるので、今回の場合は必ずしも必要ないように思います。

throw new Exception ()インスタンス化したものを投げて、catch (Exception $e) で引数としてインスタンス化したものを捕えて変数($e)に代入しています。 オブジェクト(インスタンス)名は任意ですがExceptionの「e」や「ex」がよく使われているのだと思います。

$e -> getMessage() でthrow句で投げたメッセージが取得できます。

試しに addNum() の引数に両方数字を入れた場合と、片方、両方に文字を入れた場合(例外)を入れてみました。

var_dump(addNum(1, 2));

var_dump(addNum('いち', 2));

var_dump(addNum('いち', 'に'));

両方数字の場合は「3」と出ます。

引数1つ目($a)に文字が入っている場合は「"引数1が数字ではありません"NULL」と出ます。
これは $a を検証して出た例外のメッセージと return $a + $b処理されないという意味を表しているNULLです。
この場合 '$b' を検証せずに例外が投げられています。

また、getMessage()メソッド以外にも getCode() など、クラスに応じてメソッドは色々あるみたいです。

そしてExceptionクラス以外にもInvalidArgumentException クラスなど、例外クラスは色々あるみたいです。

finally句もあります

例外が発生してもしなくても、try の下に finally のブロックを書くと、中が処理されます。

PHPでfinallyを扱う方法【初心者向け】 | TechAcademyマガジン

try,、throw、catchと併せて覚えておいたほうが良さそうです。

phpではthrow句は必要?

throw が書かれていないものを見かけたので、書かない場合はどうなるのか確かめました。

うまくロジックが書けているか疑問ですが、具体的には上のコードでtryブロックの中のif文処理を行わず、throw句を入れませんでした。
$e -> getMessage() はメッセージを設定していないのでその下に var_dump('例外です') と加えてみました。

先ほどの3パターンの addNum() を確かめた結果、順に3、2、0と出ました。

例外にならず $a + $b の処理が行われ、catchで捕まえていませんでした。

調べたところ、

【PHP入門⑫】例外処理 ~初心者でも安心!基礎から学べるプログラミング講座~ | Snome;

PHPの場合は、throwしないとcatchしてくれません。 Javaの様に勝手にthrowしてくれることは無いので、例外処理を書く場合は、ちゃんとthrowしているか確認してください。

なんと。間違っていたコードだったのでしょうか。

改めて調べました。

【PHP超入門】クラス~例外処理~PDOの基礎 - Qiita
PHPの定義済みの関数やクラスは例外を投げてくれるのか)
☆前述したExceptionクラスの種類について、catch (Exception $e) の意味についてもこちらの記事に書かれています。

定義済みの関数の多くは、エラー報告だけで例外は投げてくれないけど、エラーを例外に変換することもできるよって書いてあります。 新しい定義済みのクラスの一部は、例外を投げてくれるとも書いてあります。

throw と書かなくても例外を投げてくれる関数があるのですね。
例外を投げない関数は throw が必要なのでしょうか(これが“エラーを例外に変換”?)。

あとはtry/catch句を使う理由は、エラーで表示される内容(パスワードなど)が表に出てしまわないようにcatchする、とありました。
エラーはログに出す処理もcatch句の中に書いたりするのですね。

例外処理。読めるけど書けない類のコードだなという印象ですが、大事な部分では入れなくてはいけない処理ですね。

call_user_func_array()

コールバック関数に配列の引数を渡す場合に使う関数です。

コールバック関数とは引数の関数のことでした。

call_user_func_array(array($instance, $method), $params);
// call_user_func_array ( コールバック関数名, パラメータの配列 );

第一、第二引数ともに配列です。クラスのメソッドを指定して、そこに配列でメソッドの引数を渡して実行します。

ここでは 'array($instance, $method)' の部分をコールバック関数としています。

「【PHP】コールバック関数の使い方・サンプル(レポート出力するデータを後から差し替える)」
https://notepad-blog.com/content/95/

まだまだ根強いコールバック関数への苦手意識。早くなくしたいです。

ルーティング

ルーティングの概念を理解する【Webフレームワークの基礎知識】 | Web白熱教室

ルーティングはクライアントのリクエストとサーバーの処理をひもづける作業

phpフレームワークに限ったことではない用語でした。

既存のフレームワークで作られたサイトで新しくページを追加したときにうまく行かず(404になってしまった)、原因はルーティングが設定されていないからでした。

ルーティングではリクエストURLによってこのControllerを使う、など設定されているのですが、書き方はサイトの構造によって様々みたいです。

WordPressの場合は内部で行われているんですかね。パーマリンクの処理周りでしょうか。

そのルーティングに一役買っているのがフロントコントローラーでした!

フロントコントローラー

PHP フレームワークを理解する為のフロントコントローラとページコントローラ | h19e

フロントコントローラは、URLに関係なくすべて同一のファイルにアクセスされます。

同一のファイルにアクセスをまとめることで管理しやすいということでしょうか(未確認)。

フロントコントローラーの「コントローラー」は、MCVのコントローラー(Controller)とは別の意味で使われているそうです。

フロントコントローラー(一番上のindex.php)に転送するために.htaccessが働いているというのは盲点でした。
(普通に考えても、実体のファイルがないはずのurlでアクセスできるのは不思議なのに)

アクセスしているURLはそのままで、リダイレクトではなく転送(書き換え)しています。

例:https://example.com/hoge にアクセス
.htaccess

RewriteRule (.*)$ /index.php?r=$1

https://example.com/index.php?r=hoge に書き換え
→ GETパラメータ(r=1)によって使うクラスやControllerを決める。

というようにURLによってページ表示されています。

.htaccess mod_rewriteによる正規表現を使ったリダイレクト設定

正規表現...勉強しなくては。

GETパラメータがなくてはならないですね!

さいごに

後ろの方はボリュームが小さくなってしまいました。
大事な部分だと思うので、また復習して追記したいです。

phpの基礎力を付けるために、おなじみのTODOリストを作ってみようと思っています。
TODOリストはソースコードも用意してくださっているサイトを見ながら作るのもありですが、 今ある知識でイチからやってみるのもありかなと思っています。(あまりできる気がしない)

そういえば以前メール送信してみて深堀りするはずが頓挫しています(記事が下書きで終わっていました)。

完成まで持っていきたいです!