見出し画像

【初学者向けコードリーディング】 PHP の TODO アプリのコードを一緒に読み解こう

7968

変更履歴

2021 年 7 月
・当記事でコードリーディングする TODO アプリのコードは書籍「絶対に挫折させないアプリ開発 はじめてのLaravel」で公開(※著作権表示不要の MIT ライセンス)されていたコードです。
書籍「絶対に挫折させないアプリ開発 はじめてのLaravel」の販売が終了したため、紹介等に関する内容を変更・削除しました。(※本記事でコードリーディングする内容に変更はありません。)
当記事の筆者は書籍「絶対に挫折させないアプリ開発 はじめてのLaravel」に一切関与しておりません。
・誤字・脱字などの訂正

はじめに

PHP の入門書などを終えて、いざ自分でコードを 1 から書こうと思っても難しくて進めない方がほとんどではないでしょうか。
コードは書くよりも、読む方が簡単です。
また、コードを読むことで、書くときの参考にもなります。
入門書を終えた次のステップとしてコードリーディングをしてみましょう。
実際にプログラマーになっても、コードを書くよりもコードを読む時間の方が長い方がほとんどだと思います。
私はコードリーディングの記事や書籍を見たことありませんが、入門書を終えた次のステップとして最適です。
この記事を読みながらコードリーディングをすることで、PHP の理解が深まれば幸いです。

今回は Github 上で公開されている TODO アプリのソースコードをコードリーディングします。
TODO アプリの制作者にソースコードのライセンスを確認したら、「著作権表示不要の MIT ライセンス」とのことです。
MIT ライセンスは、自己責任において自由に利用できるライセンスです。
MIT ライセンスに従って、ご利用ください。
下記の URL からソースコードをダウンロードできます。

TODO アプリ ソースコードのダウンロード

TODO アプリは PHP で記述されており、入門書を終えた方がコードリーディングするのにコード量も内容も最適です。
説明するときにコードを抜粋して掲載しますので、当記事のみで読み進めることができますが、ソースコード全体を把握したい方は、自分でダウンロードしてファイルを開いて確認してください。
ダウンロードすると様々なファイルがありますが、public フォルダ内にある index.php を基点に見ていきます。
コード内で使われている関数の意味や、どのような処理をしているのかなど、1 つ 1 つ読み解いていきますので、一緒にコードリーディングしながら理解を深めていきましょう。
TODO アプリのコードには一部古い記述などもあるため、新しい記述の仕方も説明していきます。

ディレクトリ・データベースの構成

実際に ダウンロード してディレクトリとデータベースの構成を確認してください。

[ ディレクトリ構成 ]

┌ app.phpconfig
│ └ env.phpdb
│ └ create_tasks_table.sqllib
│ ├ functions.php
│ ├ Session.php
│ └ Validate.phpmodels
│ ├ Model.php
│ └ Task.phppublic
 ├ index.php
 ├ task-delete.php
 └ task-store.php

public に index.php などがあり、ここが公開フォルダ(閲覧者がアクセスするページ)になります。

公開フォルダを XAMPP・MAMP 環境で説明するなら、ブラウザに localhost と入力してアクセスすると htdocs 内にあるファイルが表示されます。
htdocs フォルダが公開フォルダ(閲覧者がアクセスするページ)になります。
htdocs より上のディレクトリにあるフォルダやファイルには、ブラウザなどからアクセスすることはできません。

今回の TODO アプリのコードをレンタルサーバーなどに設置すると、PHP のバージョンアップや脆弱性などの発見でメンテナスが必要になります。
TODO アプリを動かしたい方は XAMPP や MAMP 環境で試すことをお勧めします。
コードリーディングが終わって不要になったらデータは削除しましょう。

データベースの構成は、db フォルダ内の create_tasks_table.sql に記載されています。

[ create_tasks_table.sql の全文 ]

DROP TABLE IF EXISTS `tasks`;
CREATE TABLE `tasks` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(255) NOT NULL,
 `created_at` timestamp,
 `updated_at` timestamp,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB;

この記事を読んでいる方は XAMPP や MAMP 環境が多いかと思いますので、phpMyAdmin でデータベースを作成する方法を簡単に説明します。
最初は「Create database」のデータベースネームに quickstart_php と入力して「Create」を選択してデータベースを作成します。

画像7

作成したデータベースの「quickstart_php」を選択してください。
「SQL」を選択して、下記の SQL 文をコピペして「Go」を選択すると「tasks」テーブルが作成されます。

画像6

※コピペするときは 3 行目の DROP TABLE ... から選択してください。

[ create_tasks_table.sql の全文 (デフォルト値を追記) ]

DROP TABLE IF EXISTS `tasks`;

CREATE TABLE `tasks` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(255) NOT NULL,
 `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB;

これで、データベースの作成は完了です。
MySQL 5.6.6 から timestamp の暗黙的なデフォルト値には警告がでるようになりました。
そのため、ダウンロードした create_tasks_table.sql に記載された SQL 文をそのまま実行しても警告が表示されてテーブルが作成できません。
デフォルト値を追記した SQL 文(上述した SQL 文)をお使いください。

ディレクトリ構成についてもう一度見ていきます。

[ ディレクトリ構成 ]

┌ app.phpconfig
│ └ env.phpdb
│ └ create_tasks_table.sqllib
│ ├ functions.php
│ ├ Session.php
│ └ Validate.phpmodels
│ ├ Model.php
│ └ Task.phppublic
 ├ index.php
 ├ task-delete.php
 └ task-store.php

先ほども説明しましたが、public は公開フォルダ(閲覧者がアクセスするページ)になります。
ディレクトリ構成やファイル名から、どのファイルに何が記述されているのか推測することができます。
config フォルダに env.php があります。
config は設定などの意味を持つ英単語です。
env は environment の略で環境などの意味を持つ英単語です。
env.php には環境に関する記述がされてるのかと推測することができます。
ちなみに env はよく使われる表記ですので覚えておいてください。

他にも推測できることがあります。
PHP のクラス名は、StudlyCaps(単語の先頭文字を大文字で表記する)記法で記述するという習慣的なルールがあります。
絶対にクラス名を StudlyCaps 記法で記述しなければならないというわけではありませんが、クラス名を StudlyCaps 記法で記述すると決めておくことで、ひと目でクラス名と判別できます。
また、1 つのファイルに複数のクラスを記述するとわかりにくいので、1 つのファイルには 1 つのクラスを記述するという習慣的なルールがあります。
先ほどのディレクトリを見るとわかりますが、下記のファイルはクラス名と同じ StudlyCaps 記法です。

・ Session.php
・ Validate.php
・ Model.php
・ Task.php

もうお気づきかと思いますが、Session.php には Session クラスだけが書かれており、Validate.php には Validate クラスだけが書かれております。
Model.php も Task.php も同様です。
それぞれ、ファイル名とクラス名が同じになっており、ディレクトリ構成を見るだけで、どのファイルに何のクラスが書かれているのか推測することができます。

db はデータベース database の略で db フォルダ内には、データベースの構成(テーブル名やカラムの名前や種類など)について記述されています。

lib はライブラリ library の略で、lib フォルダ内には、共通して利用する関数などが記述されたファイルがあります。

ディレクトリやファイル名を見るだけで、何のファイルか、何が書かれているのか、ある程度推測することができます。
最初にディレクトリを見ても推測できなかった方は、これからはどのような意味のディレクトリ・ファイル名になっているのか、どのファイルに何のクラスや関数が書かれているのか意識してみてください。
当然、ディレクトリやファイル名はこのように設置・命名しないと動作しないなどの決まりはないので、制作者によってディレクトリの構造や名前などは異なります。
ただ、1 つのファイルに 1 つのクラスを記述する、ファイル名とクラス名を同じにするなど、習慣的なルールに沿ってファイル名やディレクトリ構造にすると、初見でもどこに何が書かれているのか推測することができます。
自分で制作するときは、ディレクトリやファイル名なども意識してください。

コードリーディングをはじめる前に config フォルダにある env.php を見てみましょう。

[ env.php の全文 ]

<?php

define('DB_HOSTNAME', '127.0.0.1');
define('DB_DBNAME', 'quickstart_php');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');

ここで、記事内のコードの記載について補足します。
コードを抜粋して記述するので、どのファイルの記述なのか、わかりにくいことがあります。
わかりやすくするために 1 行目で [ ] 内に補足を記述しています。
その場合、2 行目には空きを入れて、3 行目からコードを記述しますので覚えておいてください。

env.php を見るとわかりますが、データベースへ接続する重要な情報が記載されています。
これはデフォルトの記述ですので、自分の環境で TODO アプリを動かしたい方は、データベースの接続情報を書き換えてください。
MAMP 環境の場合になりますが、パスワードを root と設定した場合は下記のような記述になります。

[ env.php の全文( MAMP 環境の一例) ]

<?php

define('DB_HOSTNAME', 'localhost');
define('DB_DBNAME', 'quickstart_php');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', 'root');

データベースへの接続情報は基本的に変更することはないので、define 関数を使って定数で定義しています。
public フォルダにある index.php などに直接データベースの接続情報を記述することもできますが、意図的にデータベースの接続情報を config フォルダの env.php に記述しています。
env.php に切り分けることで下記のメリットがあります。

・データベースの接続情報がどこに記述されているのか把握しやすい
・記述ミスなどで php のコードがそのまま表示されてもデータベースへの接続情報は表示されない

データベースへの接続情報が 1 箇所にしかないので、どこに記述されているのか把握しやすくなります。
もう 1 つは、記述ミスなどで php のソースコードがそのまま表示されても、データーベースの接続情報は表示されないというメリットがあります。
今回の場合、public フォルダ内は誰でもブラウザからアクセスできます。
そこにデータベースの接続情報をそのまま記述したと仮定します。
そのときに、拡張子を誤って php ではなく html にすると、閲覧者が php のソースコードをそのまま確認できてしまいます。
また、下記のように php の開始タグを <?php ではなく、<php と誤って記述したときも、閲覧者が php のソースコードをそのまま確認できてしまいます。

[ 誤った記述例 ]

<php

define('DB_HOSTNAME', '127.0.0.1');
define('DB_DBNAME', 'quickstart_php');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');

データベースの接続情報が漏洩すると、場合によってはデータベースに保存した個人情報やパスワードなど重要な情報が漏洩する危険性があります。
データベースの接続情報だけを切り分けて、閲覧者がアクセスきないディレクトリに設置すると、記述ミスなどによって php のソースコードが表示されても、データベースの接続情報を直接確認できなくなります。
データベースへの接続情報の取り扱いにには注意してください。

この記事の対象者

この記事は PHP の入門書を終えて、PHP の記述の仕方を一通り理解された方が対象です。
この記事を理解するには、PHP の基本的な記述、セッションやクラス、例外処理、PDO などの知識が必要になります。
それらについて理解されていない方は、先に下記の記事に目を通してから、この記事を読んでいただくと理解できるかと思います。
全て私が投稿した記事ですので、理解しやすい説明なのかも確認してください。

Qiita -【PHP超入門】HTTP(GET・POST)について(一部有料)
Qiita -【PHP超入門】Cookieとセッションについて
Qiita -【PHP超入門】クラス~例外処理~PDOの基礎(一部有料)

この記事を理解するには、下記の知識が必要になります。

・PHP の基礎(変数・関数・クラス・PDO など)
・HTTP の基礎( GET・POST など前述した記事を理解した程度)
・HTML・CSS の基礎( index.php の HTML が読める程度)
・SQL 文の基礎( SELECT や INSERT などの基本的な記述)
・XAMPP・MAMP ( ローカル環境構築の説明はありません )

記事を読んでみて、理解不足のところがあれば上述した記事を見るか、調べてください。

TODO アプリのコードについて

・TODO アプリのコードは、MIT ライセンス(著作権表示不要)に従って、自己責任においてご利用ください。

免責・注意事項

・当記事での内容には細心の注意を払っておりますが、すべての情報の内容の正確性、完全性及び安全性等を保証するものではありません。
・当記事での内容によって生じたいかなる損害に対しても、一切の責任を負いません。
・当記事の情報については、予告なしに一部を変更または削除する場合がありますので、予めご了承ください。
・当記事は、TODOアプリのソースコードの完全性及び安全性等を保証するものではありません。

index.php のコードを中心に読んでいきましょう。
index.php にアクセスするとタスク名の入力欄があり、「Add Task」のボタンがあります。

画像16

index.php の 1 〜 2 行目のコードリーディングの途中までは無料で公開しております。
1 つ 1 つ説明しているので、全て読むのに時間がかかると思います。
例えばですが、1 日 1 行ずつと決めて読んだり、移動時間などの隙間時間を活用するなどして、記事を読んでみてください。
無料部分を読んでみて、理解しやすいと感じた方は、購入して全て読んでいただけると嬉しいです。

index.php:1 〜 2 行目

最初は、public ディレクトリにある index.php の 1 〜 2 行目の記述を見ていきましょう。

[ index.php の抜粋 ]

<?php
require_once '../app.php';                             // ←ここの記事と
require_once join_paths([MODELS_ROOT, 'Task.php']);    // ←ここの記述

$tasks   = Task::all();
$session = session('tasks');
$errors  = $session->flash('errors', []);
?>

1 行目は require_once 関数で app.php を読み込んでいます。
require_once 関数は指定したファイルを一度だけ読み込みます。
require_once でファイルを読み込むことで app.php で記述している関数などを利用することができます。
よく利用する関数などは、1 つのファイルにまとめて定義しておくことで、どこに関数が記述されているか把握しやすくなりますし、そのファイルを読み込むだけで関数などを利用できます。

2 行目でも require_once 関数を使ってファイルを読み込んでいますが、join_paths 関数を使っています。
join_paths 関数は app.php で定義されています。
app.php で定義されている join_paths 関数について見てみましょう。

[ app.php の抜粋 ]

<?php
// パスの結合へルパ
function join_paths(array $paths)
{
   return implode(DIRECTORY_SEPARATOR, $paths);
}
define('APP_ROOT', dirname(__FILE__));
define('LIB_ROOT', join_paths([APP_ROOT, 'lib']));
define('MODELS_ROOT', join_paths([APP_ROOT, 'models']));

join_paths 関数の引数には array と記述しているので、引数には配列を渡すことができます。
引数で渡した配列を implode 関数で連結しています。

implode

配列要素を文字列により連結する

引用:PHP: implode - Manual

implode 関数の第 2 引数に渡された配列の要素を第 1 引数で指定した文字列で連結します。
サンプルコードで確認してみましょう。

[ サンプルコード ]

$array = ['aaa', 'bbb', 'ccc'];
$str = implode('/' , $array);

var_dump($str);
[ var_dump の結果 ]

string(11) "aaa/bbb/ccc"

実行結果:3v4l

implode の第 1 引数で指定した / 区切りで配列の要素が連結されて、文字列になっているのがわかります。

今回、implode 関数の第 1 引数に DIRECTORY_SEPARATOR を指定しています。
定数は大文字で記述する習慣があります。
そのため、DIRECTORY_SEPARATOR は定数と推測できます。
もっと厳密に言えば、DIRECTORY_SEPARATOR は定義済み定数です。
定義済み定数とは、最初から PHP で定義されている定数です。
PHP マニュアルの 定義済み定数 にも DIRECTORY_SEPARATOR が記載されています。
DIRECTORY_SEPARATOR はディレクトリセパレータと呼び、文字通りディレクトリのセパレータ(区切り)を表します。
ディレクトリセパレータは OS によって異なります。
Windows ならディレクトリを \ 区切りで表し、mac なら / でディレクトリの区切りを表します。

[ windows の場合 ]

C:\Users\Public\Libraries
[ mac の場合 ]

/home/user/public_html

OS ごとに異なるため、ディレクトリの区切りを DIRECTORY_SEPARATOR で指定しています。
ディレクトリセパレータを利用することで、サーバーの OS が Windows でも mac でも、他の OS でも問題なく動作します。

join_paths 関数は、引数で渡した配列の要素をディレクトリセパレータ区切りで連結する関数ということがわかりました。
もう一度、index.php を見てみましょう。

[ index.php の抜粋 ]

<?php
require_once '../app.php';
require_once join_paths([MODELS_ROOT, 'Task.php']);

join_paths 関数の引数に配列で MODELS_ROOT と Task.php を渡しています。
これをディレクトリセパレータで区切って連結していることはわかりますが、MODELS_ROOT は何をしているのでしょうか。
大文字ですので定数と推測できます。
DIRECTORY_SEPARATOR とは違い、MODELS_ROOT は定義済み定数ではなく、自分で定義(以下、ユーザー定義)した定数です。
app.php で定義していますので、その記述を見てみましょう。

[ app.php の抜粋 ]

<?php
// パスの結合へルパ
function join_paths(array $paths)
{
   return implode(DIRECTORY_SEPARATOR, $paths);
}
define('APP_ROOT', dirname(__FILE__));
define('LIB_ROOT', join_paths([APP_ROOT, 'lib']));
define('MODELS_ROOT', join_paths([APP_ROOT, 'models'])); // ここの記述

define 関数で定数を定義しています。

define

名前を指定して定数を定義する

引用:PHP: define - Manual

define 関数の第 1 引数に定数の名前を指定し、第 2 引数に定数の値を指定します。
見るとわかりますが MODELS_ROOT 定数を定義するときの第 2 引数でも join_paths 関数を利用しています。
join_paths 関数は配列の要素をディレクトリセパレータ区切りで連結します。
MODELS_ROOT 定数の値は APP_ROOT 定数と文字列 models をディレクトリセパレータ区切りで連結しています。
APP_ROOT もユーザー定義定数ですが、これは何をする定数なのでしょうか。
APP_ROOT も app.php で定義されていますので、見てみましょう。

[ app.php の抜粋 ]

<?php
// パスの結合へルパ
function join_paths(array $paths)
{
   return implode(DIRECTORY_SEPARATOR, $paths);
}
define('APP_ROOT', dirname(__FILE__));                  // ここの記述
define('LIB_ROOT', join_paths([APP_ROOT, 'lib']));
define('MODELS_ROOT', join_paths([APP_ROOT, 'models']));

APP_ROOT 定数の値には dirname(__FILE__) と記述されています。
__FILE__ という記述があります。
FILE は大文字なので定数と推測できますが、前後にアンダーバー 2 つ __ を記述しています。
PHP には マジカル定数 と呼ばれる定数が 9 つあります。
__FILE__ は、このマジカル定数の 1 つで、マジカル定数は前後にアンダーバー 2 つを記述して表します。

__FILE__

ファイルのフルパスとファイル名 (シンボリックリンクを解決した後のもの)。 インクルードされるファイルの中で使用された場合、インクルードされるファイルの名前が返されます。

引用:PHP: 自動的に定義される定数 - Manual

要は __FILE__ と記述されているファイルのフルパスを返します。
サンプルコードで確認しましょう。
__FILE__ を記述した test.php を用意して、MAMP のローカル環境の公開ディレクトリ( htdocs )に設置した場合は下記のパスを取得できます。

__FILE__ の返り値( MAMP ローカル環境 ) ]

/Applications/MAMP/htdocs/test.php

ポイントはファイル名も含んだパスということです。

例えばですが、ロリポップというレンタルサーバーの公開ディレクトリに設置した場合は下記の返り値になります。

__FILE__ の返り値( ロリポップのレンタルサーバー ) ]

/home/users/○○○/○○○○○○/web/test.php

○○○ の部分は利用者ごとに異なりますが、先ほどと同じくファイル名を含むフルパスを取得できます。
ただ、先ほどのローカル環境と比べるとわかりますが、フルパスは環境によって異なります。
例えば、ローカル環境で開発しているときにフルパスをそのまま記述( /Applications/MAMP/htdocs/test.php )した場合、レンタルサーバーにアップロードして公開するときにフルパスを変更( 例:/home/users/○○○/○○○○○○/web/test.php )する必要があります。
__FILE__ を使えば、環境にあわせたフルパスを返してくれるので、その必要がなくなります。

この続きをみるには

この続き: 116,194文字 / 画像13枚

記事を購入

980円

購入済みの方はログイン
この記事が気に入ったら、サポートをしてみませんか?
気軽にクリエイターの支援と、記事のオススメができます!
7968

もしサポートしていただけたら、技術書や開発環境などの購入資金に充てたいと思います。