PHP require_onceを使わない、 autoloadを使ってクラスを読み込む方法 

私、恥ずかしながら…。(*ノωノ)

今までPHPをやっていて、autoloadという仕組みがあることは知っていましたが、ライブラリを読み込むときとかに使うぐらいでちゃんと自分で使えていませんでした。

ただ、今回あるプロジェクトを見ていて、PHPUnitがrequire_once地獄になっておる…(´ω`) と思い、どうにかしたく、autoloadに取り組むことにしました。

まず、シンプルなPHPプロジェクトを作ってみます。

環境はWindows10 Pro, PHP7.2.31です。

下記のように、classの下にHogeとMonkeyというクラスがあり、トップディレクトリにhoge.phpがあります。

hoge.phpの初期状態はこんな感じです。

<?php

echo "ほげだよ<br>";

$monkey = new Monkey();
$monkey->scream();

class/Monkey.phpの初期の中身はこんな感じです。

<?php

class Monkey
{

    public function scream(){
        echo "ウキー!!!";
    }
}

autoloadを導入するにあたり、まず必要なのはcomposerです。

composerのインストールなどは、こちらなどをご覧ください。
ここではcomposerは普通に使えるものとして進めます。

composer.jsonを次のように作ります。

{
  "autoload": {
    "psr-4": {
      "MonkeyWorld\\": "class/"
    }
  }
}

composer install

をします。

私はPHP Stormを使っていて、PHP StormではcomposerのコマンドをPHP Stormから使えるようになってるので、次の赤丸の箇所をポチリとやるだけです。便利!✧٩(ˊωˋ*)و✧

そうすると、下記のようにvendorというディレクトリができて、autoload〇〇ってファイルたちもできてますね。

composer.jsonに書いた記述の内容は、classというディレクトリ以下が”MonkeyWorld というnamespaceになって自動的に読み込みができるという内容のはずです。

hoge.phpを次のようにします。

<?php

use MonkeyWorld\Monkey;
echo "ほげだよ<br>";


$monkey = new Monkey();
$monkey->scream();

Monkeyクラスを次のようにします。

<?php
namespace MonkeyWorld;

class Monkey
{

    public function scream(){
        echo "ウキー!!!";
    }
}

上記のコードでは、namespaceを追加したんですね。

これでhoge.phpからMonkeyクラスが読み込めそうじゃないですか。

しかし、結果は

( ! ) Fatal error: Uncaught Error: Class ‘MonkeyWorld\Monkey’ not found in D:\xampp\htdocs\autoloadSample\hoge.php on line 7
( ! ) Error: Class ‘MonkeyWorld\Monkey’ not found in D:\xampp\htdocs\autoloadSample\hoge.php on line 7

となってしまいます。(>_<)

私、これautoloadのつまづきポイントだと思うんですけどね。

そりゃ、autoload.phpを読み込んでないんですもんね。なんの恩恵もないわけですよ。

<?php

require_once "vendor/autoload.php";

use MonkeyWorld\Monkey;
echo "ほげだよ<br>";


$monkey = new Monkey();
$monkey->scream();

上記のファイルでは

require_once "vendor/autoload.php";

が追加になっています。

ブラウザから実行すると、次のように表示され、うまくいきました!

次に例えば、Chimpanzeeクラスを追加します。

<?php
namespace MonkeyWorld;

class Chimpanzee
{

    public function scream(){
        echo "おいらチンパンジー。";
    }
}

これも、次のようにやるとちゃんと表示されます。

<?php

require_once "vendor/autoload.php";

use MonkeyWorld\Monkey;
use MonkeyWorld\Chimpanzee;

echo "ほげだよ<br>";


$monkey = new Monkey();
$monkey->scream();

$chimpan = new Chimpanzee();
$chimpan->scream();

ブラウザから実行すると、次のようになります。

よかった✧٩(ˊωˋ*)و✧

ちなみに、たとえばMonkeyクラスのファイルをmonkey.phpなどとしている場合があると思います。そうすると、Linuxだと読み込めなかったりするみたいです…。(自分が遭遇したわけではないので参考までですが)

https://stackoverflow.com/questions/41784405/need-to-dump-autoload-to-everytime-i-add-a-new-class

autoload使う場合は、クラス名と同じファイル名にする必要がありますが(例えばここでは、MonkeyクラスはMonkey.php)、大文字小文字も同じにした方がよいでしょう。

PHPUnitで使う話は?となりますが、長くなったので次回へ続きます!

次回はこれ↓

collectdのログ出力を抑制する

AWSのCloudWatchエージェントで使用するためにcollectdをyumでインストールして初期設定のまま運用すると/var/log/messagesに下記ログが大量に出力されます。

collectd: Available write targets: [none]

通常collectdは少なくとも1つのWriteプラグインを有効にして使用するものですが、CloudWatchエージェントから使うだけならWriteプラグインは不要です。

collectdの初期設定ではsyslogプラグインが有効になっていて、デフォルトのLogLevelはinfoになっています。下記のようにsyslogプラグインのLogLevelを、notice以上に上げるとログ出力を抑制します。

<Plugin syslog>
        LogLevel notice
</Plugin>

なお、syslogプラグインで指定できるLogLevelは、debug|info|notice|warning|errのいずれかです。

参考
https://collectd.org/documentation/manpages/collectd.conf.5.shtml#plugin_syslog

新人プログラマーが「リーダブルコード」を読んでみた(後編)

こんにちは。
以前、新人プログラマーが「リーダブルコード」を読んでみた(前編)を投稿した者です。今回は、その続きを書きます。
今回も、各セクションごとに重要なポイントの記載に感想を交えていきます。

9. 変数と読みやすさ

<重要ポイント>

  • 不要な変数を削除し、変数の数を減らす。
  • 変数のスコープをできるだけ縮める。
  • 変数が更新される回数をできるだけ減らす。

コードを書いている時って、機能のことばかり考えていて、変数はそれを実現するための手段と考えてしまいがちだと思います。すると、変数をどう扱おうかって割と抜けちゃうんですよね…。
この本を読み、コードを書いているときの変数について気にすることをマニュアル化してくれると、今後意識できそうな気がします。

10. 無関係下位問題を抽出する

<重要ポイント>

  • プロジェクト固有のコードから、汎用的な機能を取り出し、汎用コード化する。
  • 1つのコードブロックの、高レベル目標を明確にし、それ以外に気を回す必要がないコードにする。
  • 小さな関数を作りすぎると、逆に読みにくくなるのでやり過ぎないようにする。

これは結構、意識していることではあります。
というのも、複雑なことをいくつも考えることができないので、自分の中で整理しておかないと、コードが把握しきれません。(家も散らかってくるとストレス感じます。。。)
ただ、結構行き当たりばったりな整頓をしている気がするので、ちゃんと計画を立ててコードを書かないといけないですね。

11. 一度に1つのことを

<重要ポイント>

  • 「読みにくい」コードがあった時に、そこで行われているタスクを列挙する。
  • タスクを別関数化、クラス化する。
  • それ以外の部分は、関数の論理的な段落となる。

10章で、コードを整理しなくてはという話がありました。
そこへの具体的なアプローチがこの章で記述されています。
「このごちゃごちゃコード、どこから手つけます…?」→「とりあえず列挙するか…」というムーブができるのは個人的にすごく大きいと思いました。

12. コードに思いを込める

<重要ポイント>

  • コードで行っていることや、ロジックを言葉にして説明してみる。
  • そのコンテクストに沿った形でコードを書いてみる。
  • ライブラリが提供してくれるものを知ることで、コードがより簡潔となる可能性がある。

チーム開発で大事なのは、一緒に開発する仲間との意思の疎通です。人間同士が意思の疎通を図るのに、一番良いのは会話だと思います。
分かりやすいコードについてのテクニックが、ここまでの章で紹介されていましたが、結局、文脈や実際の言葉に則ったコードが、分かりやすいということを伝えてくれています。
また、これは仲間に対してだけでなく自分に対しても有用です。
言葉にすることで、思考が整理されるんですね。
分かりにくいコードを、どうにも改善できないというときに、これを試してみたいと思います。

13. 短いコードを書く。

<重要ポイント>

  • 使っていないコードを削除する。
  • 過剰な機能を持たせない。
  • 標準ライブラリに慣れ親しむ。

使っていないコードを削除するというのは、当然のこととして、過剰な機能を持たせない。というのは、非常に心に残りました。
サービスを作るときには、ユーザーの視点になって考えることが大事ですが、そうすると、あれもこれもと機能を付けてしまいがちです。
何か機能を加えるときは、それが本当に必要なのか、別の何かで代替できないかを考え、判断しようと思います。

14. テストと読みやすさ

<重要ポイント>

  • 入出力のテストを1行で記述する。
  • 分かりやすいエラーメッセージを表示するようにする。(カスタマイズも可)
  • テストが、本物コードの読みやすさを阻害したり、開発の邪魔にならないよう、やり過ぎないようにする。
  • テスト駆動開発については様々な議論があるが、テストがしやすいようにコードをかくことは有効。

テストを分かりやすく書く方法についての章です。
テストの充実は、プロジェクトの保守性に非常に効果的ですし、テストこそ、他の人がよく編集する場所だと思います。なので、重要性は言わずもがなですが、同時に「テストがしやすいようにコードを書く」ことの大事さがここに書いてあります。
これって、テストについて言っているようで、これまでの内容のおさらいにもなっているんですよね。いかに機能を取り出して関数化、クラス化するか。
関数化もやりすぎは良くない。という話がありましたが、この「テストしやすいように」というのは、1つの物差しとなるのではないかと思います。

まとめ

前半に引き続き、後半についても内容をまとめさせていただきました。
非常に読みやすく、ページ数も多くない本でしたが、内容はかなり濃厚でした。
新人プログラマーや、コードがうまく書けないという人には是非おすすめしたいです。


正直なところ、この本に書いてあったことが今すぐできるかと問われれば、無理だと思います。
なので、この記事と本を何度も読み返すつもりです。

MySQL InnoDB AUTO_INCREMENTの上限値周りでの振る舞いを検証

AUTO_INCREMENTの値が型の上限値に達した際の振る舞いが気になったので、いくつか調べてみました。
AUTO_INCREMENTのドキュメントでは情報が足りませんでした。

例えば、一意性さえ確保できれば良いテーブルのidが上限に達した際に、1から順に再度振り直したくなるようなことがあるかもしれません。(アンチパターンの香り)

影響範囲の調査とか調整とかの工数を考えると、明らかにやるべきでない操作ですが、やむを得ない事情がある場合だってあるかもしれません(弊社には今の所ありません。良かった)。そんなときのために、知見をメモしておきます。

前提

  • UNSIGNED INT 型の上限値: 4294967295
  • UNSIGNED INT型のidカラムにAUTO_INCREMENTを指定

検証

idが上限値に達したテーブルに対し、さらにINSERTを実施する

iddata
10hoge
11hoge
4294967294hoge
4294967295hoge
INSERT INTO `id_test_table` (`id`, `data`) VALUES (NULL, 'hoge');
#1062 - '4294967295' は索引 'PRIMARY' で重複しています。

重複エラーが出て、テーブルにデータは入りません。

idを指定してINSERTする

INSERT INTO `id_test_table` (`id`, `data`) VALUES (1, 'hoge');
iddata
1hoge
10hoge
11hoge
4294967294hoge
4294967295hoge

重複が無ければ、問題なくINSERTできます。

この状態で再度INSERTした場合、次に割り当てられるidはなんでしょうか。

INSERT INTO `id_test_table` (`id`, `data`) VALUES (NULL, 'hoge');
#1062 - '4294967295' は索引 'PRIMARY' で重複しています。

AUTO_INCREMENTの値がリセットされるわけではないため、都合よく「2」などにはならず、当然のように重複エラーとなります。
こんな操作で「2」が入ったら、それこそバグの原因になってしまうので幸い。

AUTO_INCREMENTの値をリセットする

それでは、今度はテーブルのauto incrementをリセットしてみましょう。

ALTER TABLE tbl_name AUTO_INCREMENT = value; でリセットできます。

ALTER TABLE id_test_table AUTO_INCREMENT = 2;

テーブル情報から、現状のauto incrementの値を確認します。

SELECT auto_increment FROM information_schema.tables WHERE table_name = 'id_test_table';
auto_increment	4294967295

ダメみたいですね。

データを一部消してAUTO_INCREMENTの値をリセットする

レコードを一部DELETEしました。

iddata
1hoge
10hoge
11hoge

この状態で、auto incrementの値をリセットしてみます。

ALTER TABLE id_test_table AUTO_INCREMENT = 15;
SELECT auto_increment FROM information_schema.tables WHERE table_name = 'id_test_table';
auto_increment	15

この場合は無事「15」になりました。

INSERT INTO `id_test_table` (`id`, `data`) VALUES (NULL, 'hoge');
iddata
1hoge
10hoge
11hoge
15hoge

問題なくINSERTされます。

「指定したidの値より大きい値を持つレコードがある場合、変更できない」という仕様のようです。

15より低い値で入れてみると、やはり更新されません。

ALTER TABLE id_test_table AUTO_INCREMENT = 10;
SELECT auto_increment FROM information_schema.tables WHERE table_name = 'id_test_table';
auto_increment	16

冒頭の「idの採番を1から振り直す」なんてことをしたい場合には、指定したい値以上のidを持つレコードを削除してやる必要がありそうです。とんでもねえなおい。

実際に「idの採番を1から振り直す」ようなケースでは、テーブルにid:1~上限値までのデータが全て残っているような状態は珍しいのではないでしょうか。

つまるところ、
「指定したい値以上のidを持つレコードを削除」は
「テーブルのレコードを全て削除」とほぼ同じ意味になりそうです。

とんでもねえなおい。

結論

テーブルの設計段階で型を十分に検討するべき。

参考

Amazon S3(AWS)でExcelファイルが開かずにブラウザで文字列での表示になってしまう

せっかくデータベースのデータを抽出してExcelファイルを作成することができたのに、ファイルリンクを踏んでもExcelファイルとしてダウンロードにならずやや焦りました。ググってもそれらしき内容が出てこなく時間を取られてしまいました。
メタデータの Content-Disposition を設定すればよいようです。PHPファイル上では、インスタンス生成後に$s3client->putObject()内で、’ContentDisposition’ => ‘attachment’を追加したらうまくいきました。

  // S3clientのインスタンス生成
    $s3client = new Aws\S3\S3Client([
        'credentials' => [
            'key' => AWS_ACCESS_KEY,
            'secret' => AWS_SECRET_ACCESS_KEY
        ],
        'region' => 'ap-northeast-1',
        'version' => 'latest'
    ]);
    
    try {
        $s3client_CSV_upload = $s3client->putObject([
            'ACL' => 'public-read',
            'Bucket' => AWS_S3_BUCKET_NAME,
            'Key' => $upload_file['file_name'],
            'SourceFile' => $upload_file['file_path'],
            'ContentType' => mime_content_type($upload_file['file_path']),
            'ContentDisposition' => 'attachment'
        ]);
        
        // アップロードファイルのURL取得
        $results['upload_name'] = $s3client_CSV_upload['ObjectURL'];
        $results['result'] = 'SUCCESS';
    } catch (Exception $e) {
        $results['result'] = 'UPLOAD_ERROR';
        errorLog($e);
    }