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リストはソースコードも用意してくださっているサイトを見ながら作るのもありですが、 今ある知識でイチからやってみるのもありかなと思っています。(あまりできる気がしない)

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

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

ドメインとサーバーを契約しました

やっと、やっと申し込みました。

お金がかかるからという単純な理由でこれまで渋っていましたが、何とか安く済ませられそうなので一念発起してドメインとサーバーを契約しました(大げさ)。

Xserverでキャンペーンがありました。

f:id:yokoyoko_115:20210503022007p:plain
Xserverのキャンペーン

キャンペーン対象のドメイン.com.net などに限られていたので迷いましたが(本当は .work にしたかった)、サーバーの契約がある限りドメインの更新料無料の言葉につられて申し込みました。

今回は、Xserverでのキャンペーンを利用したサーバー、ドメインの申し込み手順をメモします。

キャンペーンの適用は支払いが済んでから

Xserverのアカウント登録 → サーバー申し込み
の時点では「サーバーの10日間無料お試し」になり、ドメインの申し込みができませんでした。

独自ドメイン未取得で10日間特にすることがないので、支払い登録をしました。

結果内訳は下記のようになりました。

【かかった料金内訳】
サーバー初期設定:16,500 円(年間使用料1,100円x12ヶ月=13,200円含む)
ドメイン使用料: 0円 (1ドメイン無料キャンペーン)
キャンペーン:  -3,300 円(初期設定費無料キャンペーン)


支払い:     13,200円

まだお手頃価格な気がします。

支払い登録の後、ドメインを申し込みました。

サーバー・ドメイン取得後に行うこと

これで取得したドメインにアクセスしたら見れるわけではなく、ドメインの設定が必要です。

サーバーにドメイン設定をする

「サーバー管理」というボタンから「サーバーパネル」という管理画面に行きます。

ドメインの「ドメイン設定」から「ドメイン追加設定」タブを開いて独自ドメインを登録します。

f:id:yokoyoko_115:20210504000859p:plain

そしてドメインが反映されるまで待ちます(私は10〜20分でした)。

独自ドメインにアクセスすると下記のように表示されました。

f:id:yokoyoko_115:20210504001754p:plain

ファイルの構成

「ファイル管理」のボタンから、自分に割り当てられたサーバーの中身を見ることができます。

独自ドメイン設定直後の一番上の階層】
初期ドメイン
 ┗.spamassassin
 ┗autoreply
 ┗htpasswd
 ┗log
 ┗mail
 ┗public_html ←ドキュメントルート
 ┗script
 ┗xserver_php
独自ドメイン ←初期ドメインと中の構成は同じ
ssl ←中は空

ドメインごとにフォルダが用意されています。
そしてその中にはpublic_htmlを含めた各フォルダがあります。

public_html以外のフォルダについてはどう使っていくのか不明です。
空のフォルダはFTPクライアントでサーバーを見てもよく見かけるので、ここは気にしないでおきます。

さいごに

これでようやくスタート地点に立てました。

自分のサーバーがあれば、思い切ったこともできますし、壊し放題!

人様からのアクセスを気にしていないので自由にあれこれ試すことができます。

そういえばNetlifyを利用したHugoサイトで独自ドメインを充てられたと思うのでやってみます。

サブドメインも、設定してみます。

おしまい。

Hugoサイトでcssを上書きする(Theme使用)

ようやくNetlifyでHugoサイトを公開しましたが、元のThemeのままです。

少しずつカスタマイズしていこうと思います。

☆使っているテーマは「hugo-clarity」です。

scssファイルを確認してみる

デザインを変えたいのでcssを調べます。
cssファイルは見当たらず、scssファイルを見つけました。

hugo_site2021/themes/hugo-clarity/assets/sass/_custom.sass を開くと、コメントがあります。

_custom.sass

// add customs styles and general overrides here
// due to the cascading nature of css, if you try to override theme css variables in this file, those changes will not apply. Instead, override css variables in the override.sass file
// we recommend not editing this file directly. Instead, create an assets/sass/_custom.sass file at the root level of your site.
// if you edit this file directly, you will have to resolve git conflicts when and if you decide to pull changes we make on the theme

翻訳すると

カスタムスタイルと一般的なオーバーライドをここに追加します。
css のカスケードの性質上、このファイルでテーマの css 変数をオーバーライドしようとしても、その変更は適用されません。代わりに、override.sass ファイルで css 変数を上書きしてください。
このファイルを直接編集しないことをお勧めします。代わりに、サイトのルートレベルに assets/sass/_custom.sass ファイルを作成してください。
このファイルを直接編集すると、私たちがテーマに加えた変更を引き出そうとしたときに、gitコンフリクトを解決しなければならなくなります。

ということです。

override.sass ファイルとは同じ階層の _override.sass のことでしょうか。

_override.sass

// override clarity theme's _variables.sass file.
// we recommend not editing this file directly. Instead, create an assets/sass/_override.sass file at the root level of your site.
// if you edit this file directly, you will have to resolve git conflicts when and if you decide to pull changes we make on the theme

翻訳すると

クラリティテーマの _variables.sass ファイルをオーバーライドします。
このファイルを直接編集しないことをお勧めします。代わりに、サイトのルートレベルに assets/sass/_override.sass ファイルを作成してください。
このファイルを直接編集すると、私たちがテーマに加えた変更を引き出そうとしたときに、gitコンフリクトを解決しなければならなくなります。

だそうです。
んん?

ついでに _variables.sass ファイルを見てみます。

_variables.sass ファイルの中身はその名の通り変数と設定値が書かれています。

まとめると

ファイル 役割 代わりのファイル
_custom.sass カスタムスタイル(オーバーライドは適用されない) ルートディレクトリに assets/sass/_custom.sass
_override.sass _variables.sassをオーバーライド ルートディレクトリに assets/sass/_override.sass
_variables.sass 変数の設定 _override.sass

assets/sass/_custom.sass一般的なスタイルの上書き
assets/sass/_override.sass変数の設定値の変更

と考えられます。

Theme内のscssファイルに書くと、使っているテーマがGitHubで更新された時、プル(コミット?)をしたらコンフリクトするようです。
(Gitの submodule コマンド。まだ手を出さなくていいかなと思っています)

ということは、基本カスタマイズはテーマディレクトリの上のファイルを触ることになるのだと思います。

コメントに従ってassets/sass/custom.sassファイルとoverride.sassファイルを作りました。

試しにbody要素の背景色を変えてみます。

テーマ内の_variables.sass

html
  --color-mode: 'lit'
  --light: #fff
  --dark: #000
  --bg: #002538
  --haze: #f2f2f2

と書かれているので、--haze の部分を同じように書いてみます。

普段書かない書き方です。省略形でしょうか。

_override.sass

html
  --haze: linen

すると見事上書きされました!

ちなみに linen はカラーの名称です(ベージュの薄い色)。
初めて使ってみました。たくさんありますね。

cssの仕組み

デベロッパーツールを見たところ、テーマファイルの中の _base.css に変数が使われていて、その変数が _variables.sass_custom.scss で設定されていました。

そしてテーマ内のscssディレクトリ内に唯一_ の付いていない main.scss を見てみると、諸々のscssファイルを @import していました。
コンパイル不要の場合は _ を付けるはずなので、main.scss ファイルはおそらくどこかでコンパイルされているのでしょう。

図にしてみました。

f:id:yokoyoko_115:20210425032542p:plain

マップファイルが使われている

デベロッパーツールで設定元のscssファイルが確認できるということは、マップファイルが使われているということです。

実際にページで読み込んでいるのは styles.css ファイルでした。
(実際は styles. の後ろに長い謎のパラメータ?が付いています)

もしかすると main.scssコンパイルされてstyles.cssになったのかもしれません (ファイル名が異なるのが気になります)

以前の記事を確認してデベロッパーツールから styles.css を見てみると、

/*# sourceMappingURL=styles.css.map */

sourceMappingURLアノテーションがありました!
しかし styles.css.map ファイルは見つかりませんでした。

さいごに

触れるcssassets/sass/_custom.sassassets/sass/_override.sass であることがわかりました。

マップファイルとcssファイルはおそらく動的に作られているのでしょう。

まだまだ謎だらけのHugoサイトですが、デザイン変更などできるところから進めていきます!

ロゴやブログ名を変えなくては。

Hugoでサイトを作り直します

すっかりご無沙汰になってしまったHugoのサイト作り、テーマを新しいものにして作り直そうと思います。

今回はこちらのテーマにしました。

f:id:yokoyoko_115:20210321005234p:plain

去年の記事を見ながらhttp://localhost:1313/で表示できるところまで進めました。

前回作ったサイトへのアクセスもhttp://localhost:1313/ですが、サイトディレクトリの中まで移動して hugo server でhugoサーバを起動させることで切り替えています。

前回の反省点

途中で作ることをやめてしまった要因は、完璧を目指してしまったからだと思います。

Hugoを理解してから記事移行しよう、など順番を決めて自らハードルを上げてしまっていました。

今回はできるところからやっていきます!

静的サイトジェネレーターのホスティングサービスを利用する

ブログのカスタマイズも行わずに、ダウンロードしたテーマをそのまま公開します。

HugoのホスティングサービスならNetlifyが良いそうなので使ってみます。

自作のサイトを公開するならサーバーを契約してドメインを作って、とお金がかかると思っていましたが無料で作れてしまうんですね!

ゆくゆくは自分のドメインでサイトを公開したいですが、ひとまず。

参考にしたい記事

いろいろと網羅されていて今後お世話になりそうです。

Hugo で静的なサイト・ブログを構築しよう - Qiita

Netlifyの使い方が書かれています。

Netlifyで静的サイトのホスティングをする - Qiita

こちらに倣ってNetlifyのアカウント登録をしました。

「Deploy site」をクリック!

Netlifyでデプロイできず

それらしきリンクを押したらこう出てきました。

f:id:yokoyoko_115:20210322010916p:plain

「 "page not found" support guide 」のページを見てみても当然のことながら英語。

さっそく挫けそうですが、同じ境遇の方はきっといると思うので日本語で調べます。

Hugoのテーマ保存方法を振り返る

GitHubのHugoテーマのリポジトリからコピーしてくるところで、

git submodule を使いましょう。

とよく見かけるので、どういうことか調べました。

今回のテーマの「Getting up and running」の方法で“recommended”と書かれていたのがgit submodule だったのでこちらを行っていました。
git clone とどう違うのでしょうか。

こちらは今後の課題として置いておいて...。

どうにもこうにも、Netlifyの英文をいくら(翻訳して)読んでも分からなかったので、サイトを一回削除して作り直しました。

Netlifyの画面

f:id:yokoyoko_115:20210326005927p:plain
1回目のときはBuild commandが空だったような。いけるかもしれない

またもや失敗。Deploy logを読んでみる

ダメもとで logを読んでみました。

ERROR 2021/03/25 15:45:25 HUGO-CLARITY theme does not support Hugo version 0.54.0. Minimum version required is 0.63.0

訳すと

ERROR 2021/03/25 15:45:25 HUGO-CLARITYテーマはHugoバージョン0.54.0をサポートしていません。最低限必要なバージョンは0.63.0です

Hugoのバージョンをアップデートしていなかったことが原因かもしれません。
Hugoを初めてインストールしたのは去年で、それきり触っていませんでした。

ローカル環境でHugoのサイトは見れましたが、デプロイ時には問題なのかもしれません。

Hugoをアップグレードする

brew upgrade hugo 

でアップデートできました。
すると、

Upgrading hugo 0.65.3 -> 0.82.0

と出てきました。

ローカルのhugoのバージョンはもとから0.63.0以上だったみたいです。
(ローカルでサイトは見れていたので当たり前といえばそうですね)

ということは、NetlifyでのHugoのバージョンを上げる必要がありそうです。

NetlifyのHugoのバージョンを設定する

Hugo 0.20以降はバージョンを設定することになるみたいです。

方法は

  • NetlifyのBuild & deploy > Environment で環境変数を設定する
  • config.toml ファイルに設定する

2つあります。

今はNetlifyへ設定する方法で進めます。

Environmentへの行き方
Sitesからサイトを選択 > 上のメニューやサイトの概要部分のSite settings > 左のメニューにBuild & deploy があります。

f:id:yokoyoko_115:20210327002923p:plain

バージョンはローカルと揃えました。

さぁうまくデプロイできるでしょうか!?

cssが読まれていない。

残念でした。

cssなどのパスや記事などへのリンクもhttps://example.com/になっていていました。
リンク先は

f:id:yokoyoko_115:20210327003746p:plain

と出てきました。

翻訳にかけると

例示ドメイン このドメインは、文書の中で例示的に使用するためのものです。このドメインは、事前の調整や許可を得ることなく、文献に使用することができます。

???

「example」の説明な気がします。

もしやと思い、config.toml ファイルの設定を見直しました。

すると

baseurl = "https://example.com/"

とありました。ここを変えれば良さそうです。
こちらもNetlifyから設定してみます。

Environmentで設定してみる

しかし、Environmentにbaseurlを設定しても変わりませんでした。

ビルドコマンドを設定してみる

そこで、ビルドコマンドを hugo -b $DEPLOY_URL にすると良い、という情報を見つけたのでやってみました。

Netlifyのプレビュー用URLに合わせてHugoのbaseURLを変更する · Issue #11 · kai2nenobu/www · GitHub

Hugoは -b, --baseURL オプションで config.toml の baesURL を上書きできる。なのでNetlifyのビルドコマンドにそれを指定してあげればいい。

フォーラムの例では netlify.toml にコマンドを指定しているが、NetlifyのWebでもできる。具体的には Settings > Build & deplooy > Continuous Deployment > Build settings の Build command に hugo -b $DEPLOY_URL を指定すればよい。

$DEPLOY_URLにはプレビュー用のURL?が入っているのでしょうか。

すると、トップページは相変わらず崩れたままで、リンク先の記事のページは、URLの頭に英数字がたくさん並んでいました。これがプレビュー用URLっぽいです!

目指している解決法とは少し違うみたいです。

config.tomlを修正する

はじめからこの方法を取ればよかったのですが、Gitが謎の状態になっていたのであまり触りたくありませんでした。遅かれ早かれ触るのですが。

Gitの謎現象は、きっとsubmoduleを使っている影響だと思います。 (リポジトリ?が2つ並んでいる)

baseurl = "https://example.com/" の部分を修正して、あたり障りなくコミットしてプッシュしました。

ようやく表示できました!

初心者には全然簡単ではありませんでした!

今度はプレビュー画面にcssが効かなくなってしまいました。

submoduleで作られたリポジトリは、今後どういう扱いをしていけばいいのかもわかりません。

ただ更新の仕方は、GitHubにプッシュ→Netlifyでビルド&デプロイ ということは分かりました。

少しずつ調べながらHugoサイトをカスタマイズしていこうと思います!

おしまい。

gulp導入の覚え書き(後編)- gulpとnpmの使い方

gulpを利用した当初は、なすがままに設定していただきました。
コマンドもほぼはじめましてだったので、黒い画面に呪文...と、何が起きているのか理解に苦しみました。

現在理解している範囲でnpm、gulpまわりで集めた情報をまとめます。

大まかな流れ

1. homebrewをインストール  
 ↓  
2. anyenvをインストール  
 ↓  
3. nodenvをインストール  
 ↓  
4. Node.js(npm)をインストール  
 ↓  
5. gulpをインストール・・・後編ここから  
 ↓  
6. gulpをつかう  
 ↓  
7. npmをつかう

前編の続きです。
gulpのインストールからnpmの使い方までまとめます。

gulpとは

gulpはNode.jsをベースとしたビルドシステムヘルパーです。Gruntと似た目的を持って作られたツールで、gulpを使えばさまざまな作業を自動化することができます。一番の特徴はファイルの処理をストリームで行う「ストリーミングビルドシステム」です。この特徴によって複雑なタスクも細かくカスタマイズして書くことができます。

(一部抜粋)

現場で使えるgulp入門 | 第1回 gulpとは何か | CodeGrid

ビルドシステムヘルパー...。
ストリーミングビルドシステム...。
まずはざっくり理解できればと思います。

gulpはNode.jsのライブラリのうちの一つであることは覚えておきます。

☆以下「ライブラリ」だったり「パッケージ」だったり、「モジュール」だったり用語が出てきますが、厳密な違いが分かっていないのでほぼ同じ認識で進めます。
(パッケージ > ライブラリ > モジュール の順の大きさだと思っています)

5. gulpをインストールする

ようやくgulpをインストールする準備ができました。
まずはNode.jsインストール時に一緒にインストールされたnpmの設定です。

プロジェクトのディレクトリに移動して行います。

npmの初期化

Node.jsのパッケージを使うにあたり最初に行います。
下記コマンドでpackage.jsonというパッケージ管理ファイルを作ります。

npm init

対話形式でpackage.jsonの設定値を入力していきますが、この時点で必ず必要ではないのでreturn(enter)キーで進めて大丈夫です。
最後にこれらの設定値でよいかyes/noで聞かれます。

詳細はこちらを参照しましょう。

初期化処理を行う!npm initの使い方【初心者向け】 | TechAcademyマガジン

また、npm init -ynpm init -yes とオプションを付けたコマンドを打つことで、対話などを省略することができます。

中には npm init --yes と、ハイフン(ダッシュ)が2つのコマンドも見掛けました。
これはLinuxの中で「UNIXスタイル」や「GNUスタイル」などのスタイルの違いから来ているそうです。複数のオプションをつけるときの書き方も異なるので注意です。

Linux引数(オプション)のハイフン-と--の違い - Qiita

なお、npm(Node Package Manager)については今のところNode.jsのパッケージ管理ツールである、とだけ認識して進めます。

package.json 中身の主な設定

"name":パッケージ名
"version":最初は"1.0.0"。パッケージの内容を変えるときに変更する
"author":作った人の名前
"dependencies":npmでインストールしたパッケージ(モジュール)
"scripts": script コマンド のプロパティ。キーがイベント、そして値がコマンド??

npmからglupをインストールする

npmコマンドでNode.jsのパッケージであるgulpをインストールします。

npm install gulp 

glupのバージョン確認

gulpのインストールが終わったら、

gulp -v

と、バージョンを確認しましょう。

npmでのパッケージのインストールについて

引っかかった部分や、覚えておきたい内容をまとめました。

パッケージのインストール

方法はオプションによって異なります。

npm install パッケージ名 -P

npm install パッケージ名 --save-prod
npm install パッケージ名 --save
npm install パッケージ名(オプションなし)と同じです。
package.jsondependencies フィールドに追加されます。

npm install パッケージ名 -D 

npm install パッケージ名 --save-dev と同じです。
package.jsondevDependencies フィールドに追加されます。

npm install -g パッケージ名

パッケージのグローバルインストールです。
パッケージのグローバルのインストールはお薦めされていません。

package.jsonに書かれているパッケージをインストールする

npm install

同じプロジェクトを複数人で開発する時に使うと便利なコマンドです。
Gitでpackage.jsonを管理しておくと、複数人の環境を npm install で揃えることができます。

上記のようにオプションなしではpackage.jsonのdependenciesとdevDependencies両方に書かれているパッケージをインストールします(npm install --production でdependenciesのパッケージのみインストール)。

dependenciesとdevDependenciesの違い

「dependencies = 依存(複数)」、「development = 開発」です。

ここではパッケージを開発用公開用に分けて考えます。

いつもプロジェクトで作るパッケージは開発用です。npm init でpackage.jsonを作ってパッケージを設定します。

これとは別に、公開用のパッケージ、すなわちインストールされるパッケージを作る場合はdependenciesとdevDependenciesを使い分ける必要があります。

【いまさらですが】package.jsonのdependenciesとdevDependencies - Qiita

パッケージを使いたい人がnpm install パッケージ名としたときにはdependenciesに書かれているパッケージのみがインストールされます。

公開パッケージを作る、というのは中々ないかなと思います。
開発のためのパッケージの使い方をどうすればいいか考えたところ、

npmのpackage.jsonと依存関係を理解しよう! - bagelee(ベーグリー)

全てdependenciesに追加してしまってもいいのですが、これらの違いはNode.jsサーバーとして動かすときやパッケージを公開するときに影響してきます。普段から、プログラムの動作には必要なく開発やテストのときのみに使うパッケージはdevDependenciesとして追加するように意識するといいでしょう。

開発やテストの時に使うパッケージとプログラムの動作に使うパッケージが異なる、というイメージがあまり持てず、そのパッケージに依存しているパッケージは全部必要に思えてしまうのですが、そういう場面に直面しなと実感できなさそうです。

なにはともあれ、開発だけのパッケージの場合はdevDependenciesに入るようにインストールする習慣をつけようと思います!

パッケージの開発、インストールまとめ

開発パッケージの場合 インストールされるパッケージの場合
パッケージに対して使うコマンド npm install npm install パッケージ名
コマンドのはたらき package.jsonに書かれているパッケージをインストールする
(他の人とパッケージの内容を共有できる)
パッケージをインストールする
インストール先のpackage.jsonへの追加(dependencies/devDependenciesはオプションで選べる)
dependenciesの
パッケージ
devDependenciesの
パッケージ

--productionをつけるとインストールしない

gulpのインストールはグローバルなのかローカルなのか、両方なのか問題

2021年現在ではローカルのみにインストールするのが主流のようです。

グローバルインストールでは、パスが通っている状態なので、gulp とコマンドを打つと、グローバルなgulpの実行になります。

なのでローカルのみのインストールでは不便なのかというと、npm-scriptnpx(後述)を使えば簡単にローカルのgulpを実行することができます。

問題の「両方」については、

gulpのインストールは通常ですとローカルとグローバルの両方にインストールする必要があります。グローバルにインストールされたgulpは、ローカルにインストールしたgulpを実行するのが役割です。

とのことで、仕組みは以下のように、

gulpのアプローチ "なぜグローバルとローカルにインストールが必要なのか" | じまぐてっく

グローバルなgulpのコマンドを叩くことで、ローカルなgulpを実行することができるのだと解釈しました。

現在のgulpのバージョン(4.0.2)では同じコードを見つけられなかったのですが、仕組みは同じではと思いました。

☆ローカルのみにgulpをインストールした環境で以下進めています

package-lock.jsonとは

まずは「ロックファイル」について調べました。

ロックファイル (lock file)
https://wa3.i-3-i.info/word12436.html

排他制御というのをするファイルなのですね。トイレの例を覚えよう。

他の言語などの場面では .lock という拡張子のロックファイルがあったりするみたいです。

そろそろlockファイルを理解するための最初のページ【composer.lock/package-lock.json】 - Qiita

ロックファイルの働きが、実際の作業の流れで見ていくとわかりやすかったです。

パッケージの公開も、こんなに簡単にできてしまうとは驚きでした。

こちらでも簡単にまとめられています。

【Node.js】package.jsonとpackage-lock.jsonについて簡単にまとめる - Qiita

買うものリストと領収書!

これらをまとめると

  • モジュールをインストール、またはアップデートする時、package.jsonにインストールするモジュールとバージョン(キャレット^やチルダ~付き)が書き込まれる→node_modulesフォルダにモジュールが入る→package-lock.jsonにインストールされたモジュールとバージョンが書き込まれる
  • package-lock.jsonはnode_modulesの中身やpackage.jsonによって自動で作成・変更される
  • npm install をする時、package-lock.jsonがあると、package-lock.jsonに書かれているバージョンのパッケージがインストールされる
  • npm install をする時、package-lock.jsonがないと、package.jsonに書かれているパッケージの新しいバージョンがインストールされる(package.json^2.0.0 と書かれていたら、2.5.0がインストールされることもある)

大体あっていると思います。

package-lock.jsonもGitで管理しましょう!

6. gulpをつかう

gulpfile.jsを作る

制作に便利なタスク(処理)をgulpfile.jsに書いて、コマンドで実行します。

2021年3月現在はgulp4が最新なので、その書き方で覚えていきます。

主に以下の記事を見ながら書きました。

Gulp4の変更点と新しい書き方 - Qiita

gulp4の設定方法 - SassやAutoprefixer、ejs、画像の圧縮などを自動化する | 夢みるゴリラ

調べるとgulp3を使っている人向けの記事ががほとんどなので、3と4の違いも分かります。同時に混乱します。

とても簡単にgulp(gulp4)の書き方をまとめると

  • task() を使わずに、タスクの関数宣言をする(無名関数を変数に入れたり、関数名を付けたり)
  • タスクの関数の中は大体 return ではじまる
  • exports.gulpタスク名 = タスク名(変数、関数) でgulpコマンドにタスクを設定する
  • gulpタスクの実行コマンドは gulp gulpタスク名(※gulpのローカルインストールでは実行できない)
  • exports.defalut とgulpタスク名を defalut にすると、実行時に gulp default と打たなくてよくなる(gulp でdefalutタスクが実行される)

という感じです。 肝心のタスクの中身は 心のタスクク中身はgulpのpipe()series()parallel()メソッドを使って、 処理をつなげたり、並行処理したりします。

何とかコードは読めはするものの、自力では書けないのでもう少しがんばりたいです。

***

ひとつ、冒頭のrequireの書き方で気になった部分があったので調べました。

const { src, dest, watch, lastRun, parallel, series } = require("gulp");

分割代入によりオブジェクトの特定のプロパティだけを単独変数に取得する (Object destructuring) | まくまくJavaScriptノート

これはES2015から使える分割代入というのですね。

gulpの中身はオブジェクトで、

const gulp = {
  'src': 'srcValue',
  'dest': 'destValue',
  'watch': watchVvalue'
}

などとなっているのかな、ーと思いましたが、ざっと見る限りそうでもありませんでした。
安直でした(笑)
実際どうrequireされているのか不明です。

const { src, ... のように書くと、gulpメソッドをつかう時に gulp.src ではなく src で済む、というものでした。

gulpコマンドでgulpタスクを実行する

ここではnpmの働きと分けるためにgulpコマンドから実行することを考えます。

先ほど「gulpタスクの実行コマンドは gulp gulpタスク名(※gulpのローカルインストールでは実行できない)」
と書きました。

gulpをローカルのみでインストールしている場合は簡単なコマンドではうまくいかないのです。

たとえば

gulpfile.js(一部)

// Sassファイルを監視し、変更があったらSassを変換するタスク
const watchSassFiles = () => watch('./scss/**/*.scss', compileSass);

// タスクの宣言
exports.watch = watchSassFiles;

と書いた場合、gulpfile.jsがある階層で
gulp watch とコマンドを叩けば実行できるかというとできません。

「command not found: gulp」(gulpコマンドが見つかりません)と返ってきます。
ローカルなgulpにはパスが通っていないのです。

なので./node_modules/.bin/gulp watch と叩けば実行できます。
これは毎回実行するのが大変です。

そこで便利なのがnpmです!

7. npmをつかう

npm コマンドからgulpのタスクを実行すると、コマンドもシンプルです。
また npx コマンドも使えるのでそれぞれ見ていきます。

npm-script

Node.jsユーザーなら押さえておきたいnpm-scriptsのタスク実行方法まとめ - ICS MEDIA

npm-scriptsとは、package.jsonファイルに記述可能なシェルスクリプトエイリアスです。エイリアスとはコマンド名を別のコマンド名に置き換えることです。

gulpfile.jsのたとえを受けてpackage.jsonが次のように書かれているとします。

package.json(一部)

 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "sass": "gulp sass",
   "start": "gulp watch"
}

echo \"Error: no test specified\" && exit 1シェルスクリプト(コマンド)で、これを test という別のコマンド(スクリプト名)に置き換えています。

実行するコマンドは

npm run test
npm run sass
npm start

になります。
npm runrunを付けます。

なおスクリプト名がstart、testの場合は run は不要です。
(↑は npm test でもOK)

runにまつわるお話も見つけました。

`npm run` は正式なコマンドじゃなかった件(NPMおれおれAdvent Calendar 2019 – 01日目) | Ginpen.com

gulpに関わらず、いろいろとコマンドを設定して使いやすくできるようです!

npx

npxはnpmのバージョン5.2.0以降に入っています。

npm 5.2.0の新機能! 「npx」でローカルパッケージを手軽に実行しよう - Qiita

ローカルにインストールしたnpmパッケージを、npxコマンドだけで実行できるようになります。

なので上記のnpmコマンドの代わりにnpxを使うと、

npx gulp sass
npx gulp watch

で実行できます。

こちらもgulp以外にもインストールしたパッケージを実行する時に、手軽にできて便利です。

さいごに

gulpとnpmのことは、ずっとまとめようと思って温め続けていました。
ようやく書けました。スッキリ!
(後々見返して意味が分かるのかは怪しい)

存在知りたての頃よりだいぶ分かってきたと思います。

私の場合はnpmの入り口がgulpでしたが、他の用途や機能の異なるパッケージも使っていけばもっと体系的に分かってくるのだろうと思います。

おしまい!