データ構造(複数のデータのしまい方・整理の仕組み)
ここまでの章では、1つの値を箱(変数)に入れて扱う方法を学んできました。
しかし、プログラムでは「1つの値」だけで済むことのほうが珍しく、現実のアプリではほとんどの場合、複数のデータをまとめて扱う必要があります。
例えば…
- 買い物かごに入っている商品一覧
- ユーザーの友達リスト
- ガチャで出てきたキャラクターの一覧
こんなふうに、今度は複数の値を大きな箱(配列)に一気に入れて管理する方法を勉強していきましょう。
配列(複数の値を入れる大きな箱)
例えば、果物のすべての種類のリストを作る必要があるとします。
これまでのやり方だと…
<?php
$fruit1 = "りんご";
$fruit2 = "バナナ";
$fruit3 = "ぶどう";
// …果物はぜんぶで2000種類以上あるとされてるので、この後も変数の代入がめちゃくちゃ続くとする。果物って2000種あるねん。
// ここで、2000以上代入した変数の一覧をすべて表示しなきゃいけなくなったら…?
echo $fruit1;
echo $fruit2;
echo $fruit3;
// …これを2000回以上繰り返さなきゃいけない。めっちゃ地獄!!
?>
大変すぎますよね。
でも、ここで配列を使うとこうなります。
<?php
$fruits = ["りんご", "バナナ", "ぶどう", /* …あと1997個 */];
// これが配列の書き方。一つの変数$fruitsに複数の値を入れている。
foreach ($fruits as $fruit) {
// foreachは「配列の中身を1つずつ順番に取り出して使うためのループ」
// さっき作成した配列$fruitsから値を順番に取り出して$fruitに代入するのを繰り返している
echo $fruit; //2000回繰り返して$fruitを画面に表示する
}
?>
りんご
バナナ
ぶどう
…(配列に入っているすべての果物が順番に表示される)
こんな感じで、配列を使えば先ほどのように2000行の echo を書く手間を省くことができます。
配列の値はこのように、順番に0からインデックスと呼ばれる番号が割り振られていきます。
| インデックス | 0 | 1 | 2 | … |
|---|---|---|---|---|
| 要素 | りんご | バナナ | ぶどう | … |
配列のインデックスが0からスタートするのは他の言語でも同じです。
インデックスは英単語だと見出し、索引などの意味があります。
本は目次にページ番号があるから自分が読みたいところをすぐ探しやすいですよね。
それと同じで、配列もインデックスがあるから探している値を見つけやすくなります。
例えば、「果物って2000種あんねん。」の2000種めって何がくるのか気になりませんか?
そういう時はこんなふうにコードを書きます。
<?php
$fruits = ["りんご", "バナナ", "ぶどう", /* …あと1997個 */]; // 配列$fruitsを作成
echo $fruits[0]; // 0番 → りんご
echo $fruits[1]; // 1番 → バナナ
echo $fruits[2]; // 2番 → ぶどう
echo $fruits[1999]; // 1999番 → 何かな?
?>
りんご
バナナ
ぶどう
Ziziphus(ジジフス)
ジジフス!?
ここまでをまとめると、配列は「複数の値をひとつにまとめる大きな箱」、インデックスは「取り出すための番号」と覚えればOKです。
連想配列(複数の値を入れる”仕切り”のついた大きな箱)
さっき果物の配列を作ったので、今度は果物の連想配列を作ります。
例えば、あなたが果物屋さんだとします。
- りんご:りんご1個あたりの売値・仕入れ値、りんごの在庫
- バナナ:バナナ1個あたりの売値・仕入れ値、バナナの在庫
- ぶどう:ぶどう1個あたりの売値・仕入れ値、ぶどうの在庫
こんな感じで、お店で売っている果物の名前、1個あたりの売値と仕入れ値、その果物の在庫…と、一つの商品だけでも複数の情報を把握する必要がありますよね。
連想配列ならこれを綺麗に管理することができます。
先ほど配列を複数の値が入る大きな箱と例えましたが、連想配列はそれに仕切りがついためちゃデカい箱だと思ってください。
<?php
// 連想配列:果物1種類につき、名前・売値・仕入れ値・在庫の情報をまとめて管理
$apple = [
"name" => "りんご",
"price" => 2000, // 売値(高すぎ)
"cost" => 50, // 仕入れ値(安すぎ)
"stock" => 1000 // 在庫(めっちゃ残っとる)
];
$banana = [
"name" => "バナナ",
"price" => 1800,
"cost" => 30,
"stock" => 750
];
$grape = [
"name" => "ぶどう",
"price" => 2500,
"cost" => 100,
"stock" => 500
];
// echoで中身を表示してみる
// [果物の名前](.は文字を結合):売値[果物の売値]円 / 仕入れ値[果物の仕入れ値]円 / 在庫[果物の在庫]個 \nは改行
echo $apple["name"] . ":売値 " . $apple["price"] . "円 / 仕入れ値 " . $apple["cost"] . "円 / 在庫 " . $apple["stock"] . "個\n";
echo $banana["name"] . ":売値 " . $banana["price"] . "円 / 仕入れ値 " . $banana["cost"] . "円 / 在庫 " . $banana["stock"] . "個\n";
echo $grape["name"] . ":売値 " . $grape["price"] . "円 / 仕入れ値 " . $grape["cost"] . "円 / 在庫 " . $grape["stock"] . "個\n";
?>
りんご:売値 2000円 / 仕入れ値 50円 / 在庫 1000個
バナナ:売値 1800円 / 仕入れ値 30円 / 在庫 750個
ぶどう:売値 2500円 / 仕入れ値 100円 / 在庫 500個
さっきのコードを表にするとこんなイメージです。
| 商品名 | 売値(price) | 仕入れ値(cost) | 在庫(stock) |
|---|---|---|---|
| りんご | 2000円 | 50円 | 1000個 |
| バナナ | 1800円 | 30円 | 750個 |
| ぶどう | 2500円 | 100円 | 500個 |
配列は値を「番号」で取り出しましたが、
連想配列では "name" や "price" といった 名前(キー)で中身を取り出す のが特徴です。
番号ではなく名前で取り出せるので、
「大量の情報を扱う時でも何がどれなのか迷わず管理できる」 のが強みです。
コードの再利用と構造化(同じ処理をムダなくまとめる考え方)
ここまでの章では、
変数・定数・型・条件分岐・ループ・配列/連想配列 を使って、データを扱ったり処理を繰り返したりできるようになりました。
ただ、プログラムを書き進めていくと必ずこう思う瞬間がやってきます。
「あれ?さっきと同じ処理をまた書いてる…」
例えば
- 画面にユーザー名を表示する処理
- 合計金額を計算する処理
- データを整形して返す処理
など、同じ処理をいろんな場所で何度も使うことがあります。
それを毎回コピペして書いていたら、コードは長くなって読みにくくなり、ミスやバグの原因にもなります。
そこで登場するのが コードを再利用できる「仕組み」 です。
処理をひとまとめにして名前をつけておくことで、
必要なときに呼び出すだけで同じ処理を実行できるようになります。
- コード量が減る
- 修正がしやすくなる
- 他の人が読んでもわかりやすくなる
というメリットがあり、プログラミングにおける超・基本テクニックです。
この章では、
コードを「部品」にして使い回せるようにする方法について解説していきます。
関数(命令をひとかたまりにして名前をつけたもの)
関数は、プログラムの中で 「よく使う処理をまとめて名前をつけたもの」 です。
さっきの章でも少し登場しましたが、ここであらためて整理しておきましょう。
例えるなら、毎回レシピをゼロから書くのではなく
「カレーの作り方」というセットをひとかたまりにして、名前をつけて保存しておくイメージです。
関数は、次のようなメリットがあります。
- 同じコードを何度も書かなくて済む
- 間違っている部分を修正すれば、関数を使っている全ての処理が一括で直る
- コードが短くなり読みやすくなる(バグも減る)
では、関数がどういう書き方をするのか実際にコードで見てみましょう。
<?php
// function 関数の名前(){この中に命令のリスト} ←これが関数の書き方
function make_curry() { // ← カレーを作る関数
echo "野菜と肉を切る\n";
echo "具材を炒めて水を入れて煮込む\n";
echo "ルーを入れて完成!\n";
}
make_curry(); // ← 関数を呼び出す(実際にカレーを作る)
?>
野菜と肉を切る
具材を炒めて水を入れて煮込む
ルーを入れて完成!
これで、今後はたった1行のmake_curry(); で何度でもカレーを作ることができますね
引数(関数に材料を渡す仕組み)
関数はそれだけでも便利ですが、実際のアプリでは毎回まったく同じ処理をするとは限りません。
例えば…さっきカレーを作るコードを書きましたけど、日常で毎回同じ味のカレーばっかり食べていると飽きますよね。
ビーフカレーを作りたい
チキンカレーを作りたい
ポークカレーを作りたい
となったら、いちいち make_beef_curry() / make_chicken_curry() / make_pork_curry() と関数を増やすのは大変ですね。
そこで役立つのが 引数です。
引数とは
「関数にわたす材料・情報」のことです。
名前だけ変えればどんな種類のカレーにも対応できるようにしてみましょう↓
<?php
function make_curry($meat) {
// $meat が引数(関数に渡す材料・情報)
echo $meat . "カレーを作ります\n";
echo "野菜と肉(" . $meat . ")を切る\n";
echo "具材を炒めて水を入れて煮込む\n";
echo "ルーを入れて完成!\n";
}
// それぞれ違う“材料”を渡して関数を呼び出す
// 同じ関数make_curry("引数$meatに食べたいお肉を入れる")
make_curry("ビーフ");
make_curry("チキン");
make_curry("ポーク");
?>
ビーフカレーを作ります
野菜と肉(ビーフ)を切る
具材を炒めて水を入れて煮込む
ルーを入れて完成!
チキンカレーを作ります
野菜と肉(チキン)を切る
具材を炒めて水を入れて煮込む
ルーを入れて完成!
ポークカレーを作ります
野菜と肉(ポーク)を切る
具材を炒めて水を入れて煮込む
ルーを入れて完成!
めちゃくちゃたくさん作ったな
戻り値(結果を受け取って使えるように返すもの)
前の章で、関数は「命令をひとかたまりにして名前をつけたもの」だと説明しました。
関数には大きく2つの役割があります:
1. 処理をまとめて実行する
2. 実行した結果を返す ← これが「戻り値」
PHPの書き方はこう↓
return 値;
このreturn、初めて見るとechoとの違いがよくわからなくて混乱しやすいのですが…
| 書き方 | いつ使われる? | 結果 |
|---|---|---|
| echo | 今すぐ画面に見せたい時 | その場で文字が表示される |
| return | 結果をあとで使いたい時 | 表示せず、結果を外へ返す |
こんな感じで、戻り値は結果を画面に表示せずに後で使えるようにするために使います。
実際にコードで使い方を見てみましょう。
<?php
function make_curry($meat) {
$dish = $meat . "カレーの完成!"; // $dishに文字列「$meat カレーの完成!」を代入
return $dish; // ← 戻り値の設定。ここでは画面に表示せず、結果($dish)だけを外へ返す
}
$curry = make_curry("ビーフ"); // 関数を呼ぶ(ビーフで作る)→ 出来上がった結果を $curry に保存
echo $curry; // 好きなタイミングで画面に表示する
?>
ビーフカレーの完成!
後で混乱しないように、ここまでの引数と戻り値の違いをさっと確認しておきましょう↓
| 用語 | 役割 |
|---|---|
| 引数 | 関数に渡す材料 |
| 戻り値 | 関数が作って返す結果 |
モジュール化(機能を役割ごとに分けて整理する考え方)
1つのアプリを完成させるために、数百行〜数千行、ものによっては数万行のコードを書くことになります。
気が遠くなるね。
ではその膨大な行数のコードを、1つのファイルに全部書いたら一体どうなると思いますか?
- どこに何が書いてあるのか分からなくなる
- 少し修正しただけなのに、関係ない場所まで壊してしまう
- 同じ処理を何度もコピペすることになり、修正漏れが発生する
- 自分が書いたコードなのに、後から読むと意味が分からない
- デバッグ(バグ探し)がどこに原因があるかわからなくてめちゃくちゃ大変になる
- アプリ開発は基本的に複数人でやるのに、1つのファイルだけだから同時に作業しづらい
- 別の人がファイルを見た瞬間に「そっと閉じる」
デメリットだらけですね。
こんなふうに一緒に作業してくれる仲間たちにファイルをそっと閉じられないようにするために、モジュール化という考え方があります。
モジュール化とは、機能を「役割ごとに分けて整理する考え方」のことで、プログラミングにおいては主にコードを複数のファイルに分けることになります。
例えばこんなコードがあったとします↓
<?php
// ========================================
// ぜんぶ1ファイルに詰め込んだ例(モジュール化してない)
// 最終的に「カツカレー」を完成させる
// ========================================
function make_katsu() {
// トンカツを作る処理(超ざっくり)
return "トンカツ";
}
function make_curry_roux() {
// カレーのルーを作る処理(超ざっくり)
return "カレー";
}
function cook_rice() {
// ごはんを炊く処理(超ざっくり)
return "ごはん";
}
function make_katsu_curry() {
// ↑の全部を合体させてカツカレーを作る
$katsu = make_katsu(); // 関数を実行して「トンカツ」を受け取る
$curry = make_curry_roux(); // 関数を実行して「カレー」を受け取る
$rice = cook_rice(); // 関数を実行して「ごはん」を受け取る
return $rice . " + " . $curry . " + " . $katsu . " = カツカレー完成!";
}
// 実行(最終的にカツカレーを作る)
echo make_katsu_curry();
?>
ごはん + カレー + トンカツ = カツカレー完成!
このコードだと超ざっくりですが、本当にカツカレー作るためにはご飯炊いておいてカレー煮込みながら豚肉に衣つけて油で揚げて…って果てしない作業があるのと同じように、実際はもっとそれぞれの関数に長いコードがあるものだと思っておいてください。
で、このコードをモジュール化、複数のファイルで分けるなら、こんな感じがいいでしょう。
/project(フォルダ)
├── main.php(合体して完成させるファイル)
├── /modules(フォルダ)
├── katsu.php(トンカツ担当ファイル)
├── curry.php(カレー担当ファイル)
└── rice.php (ごはん担当ファイル)
このファイル構成にそって、コードを書いていくとこうなります。
<?php
// トンカツを作る部品(トンカツ担当)
function make_katsu() {
return "トンカツ";
}
?>
<?php
// カレーのルーを作る部品(カレー担当)
function make_curry_roux() {
return "カレー";
}
?>
<?php
// ごはんを炊く部品(ごはん担当)
function cook_rice() {
return "ごはん";
}
?>
そして、これらを最終的に合体させるのが以下のファイル。
<?php
// ========================================
// main.php(メイン)
// ここで部品を読み込んで、合体して完成させる
// ========================================
// 部品(モジュール)を読み込む
// requireは「他のファイルの中身をここに読み込んで使う」というPHPの命令
// __DIR__ は、このファイル(main.php)が置かれているフォルダの場所を表す
// "."(ドット)は文字列をくっつけて、読み込みたいファイルの場所を作っている
require __DIR__ . "/modules/katsu.php";
require __DIR__ . "/modules/curry.php";
require __DIR__ . "/modules/rice.php";
// 合体して「カツカレー」を作る
function make_katsu_curry() {
$katsu = make_katsu(); // トンカツ担当の部品を使う
$curry = make_curry_roux(); // カレー担当の部品を使う
$rice = cook_rice(); // ごはん担当の部品を使う
return $rice . " + " . $curry . " + " . $katsu . " = カツカレー完成!";
}
// 実行
echo make_katsu_curry();
?>ごはん + カレー + トンカツ = カツカレー完成!
ここからもし「カツカレーにオムレツとエビフライとナポリタンを追加できるようにしてくれ」という驚異的な食いしん坊さんが現れたら、modulesフォルダに同様にオムレツ、エビフライ、ナポリタンの3つのファイルを追加することになります。
そうすれば、初めてmodulesフォルダを見た人でも一目でこのプログラムが何の料理を作れるのかを理解することができますね。
このように、コンピュータではなく人間に分かりやすいよう機能ごとにファイルを分ける考え方がモジュール化です。
ディレクトリ構成(ファイルの置き場所ルール)
突然ですが、あなたの家の中が突然、玄関に包丁、お風呂に布団、ベランダにテレビ、という配置になったらもうパニックですよね。
置き場所がめちゃくちゃでパニックにならないよう家の中を整理整頓するのと同じように、ファイルの置き場所のルールを決めておくことをディレクトリ構成といいます。
ディレクトリ構成もモジュール化と同様に、コンピュータではなく人間がファイルを見つけやすくするためのルールです。
実際にWebサイトやアプリなどを作る場合、基本的には以下のような構成になることが多いです。
/project
├── index.php (サイトの入口・最初に実行されるファイル)
├── config.php (定数・設定値だけをまとめたファイル)
├── functions.php (いろんな場所で使う共通の関数をまとめたファイル)
├── /modules
│ ├── user.php (ユーザー関連)
│ ├── auth.php (ログイン・認証)
│ └── product.php(商品関連)
└── /views
├── header.php (画面の上部分)
└── footer.php (画面の下部分)
ディレクトリ構成はサイトやアプリによって異なるので、今はなんとなく「ファイルの置き場所が決まってるんだな」と思っておけばOKです。
コメント(プログラマーのメモ/説明書き)
もうすでにコメントに関する説明をしていますので、ここでは実際にプロがどんなコメントを残すのかを紹介しておきます。
これまでの例のコードだと、こんな感じで初心者向けのコメントをしてきましたが…
<?php
function make_curry($meat) {
// $meat が引数(関数に渡す材料・情報)
?>
プロがこれを見たら「言われなくてもわかっとるがな、そんなんは…」となるので、こういったコードを一目見ればすぐわかるコメントを残すのはやめておきましょう。
プロ相手にでも残しておくべきコメントは主にこの2つです。
- 長いコードを読み込まないとわからないこと
- 言われないとわからないこと
まず、長いコードを読み込まないとわからないことのコメントはこんな感じです。
// ================================
// ユーザー認証関連の処理をまとめたファイル
// ================================
<?php
// 前提:ログイン済みのユーザーのみ呼び出される
function update_profile() {
...
}
// 仕様:1日1回までしか実行できない
function update_day() {
...
}
?>こんな感じで、冒頭の見出しや前提条件、仕様など、長いコードを読み込めばわかるけど、一言コメントがあれば工数の削減につながるので助かりますよね。
そして、言われないとわからないことのコメントの例はこちら。
<?php
...
// 古い端末との互換性のため、あえてこの書き方にしている
...
// 注意!この値を変更すると決済金額がズレる可能性あり
...
// この形式はAPI側の都合で変更不可
...
// 一時対応:将来的に修正予定
...
$status = 3; // 3 = 完了状態
?>こんな感じで、「急に出てきた3って何やねん!?」みたいなコードを読み込んでもわからないことはちゃんとコメントを残してあげた方がプログラマーがブチギレずにすみます。
