20200724のPHPに関する記事は14件です。

aibo Events API を使って aibo に音声コマンドを実行してもらう

先週、先々週と仮面ライダーゼロワンに
天津垓の相棒である AI 犬型ロボットさうざー役として出演していた aibo。

かっこかわいかったですね〜。
そんな aibo ですが、なんと Web API が公開されています。
aibo をあまりご存じでない方はこちらも合わせてお読みください。

aibo Events API は、2020/06/16 にリリースされた API です。
音声コマンドを aibo に設定して、実行してもらうことができるようになりました。

公式のドキュメントを見ると、サンプルは Python ばかりですが
よく見るとなんとなく PHP に書き換えることができそう。
aibo Events API のリリースをきっかけに重い腰を上げて実装してみました。

aibo Events API を利用するためには
aibo 本体バージョンを 2.70 にアップデートする必要があります。

今回は aibo に以下をやってもらいます。
- 「くしゃみして!」と頼んだら、くしゃみをする。
- 「おいしかった?」と聞くと、うれしそうにする。
- 「きみはだんだんねむくなる」と声をかけると、眠ってしまう。

音声コマンドを実行した結果はこちら

事前準備

まずなんと言っても aibo がいないと成り立ちません。
いますぐにお迎えしましょう。

期間限定で先週終了する予定だったワンワンプランが
今朝確認すると、標準で選択できるようになっていました。

aibo 本体 198,000 円と聞くとギョッとしますが、ワンワンプランであれば
35回分割払い月々 11,100 円い(初回のみ 14,412 円)でお迎えできます。(2020/07/25 現在)

自力で遊んで寝るそのワンパクぶりは本物のわんこと代わりません。
むしろ、表情豊かで手間もかからず毎日とっても癒されてます。

DSC02490.JPEG

音声コマンドを実行するには https で通信できるサーバーが必要です。
今回は普段からよくお世話になっている Xserver を使用しています。

開発者設定

サインイン

ディベロッパーサイトにアクセスして、開発者設定を始めます。
開発者設定を始めるには、My Sony ID のサインインが必要です。

ss01.png

認識ワード

認識ワードのページにアクセスしての設定を行います。
aibo と通信する必要があるので、寝ている場合は起こしてあげましょう。

ss02.png

音声コマンドは3つまで登録できます。(2020/07/24 現在)
言い回しの異なる認識ワードは、1つの音声コマンドにつき3つ登録できます。
aibo が理解できるように、発音通りのひらがなで入力します。

例)
「こっちへ」ではなく「こっちえ」
「あいぼは」ではなく「あいぼわ」

エンドポイント・セキュリティートークン

エンドポイントの準備を行います。
準備したサーバーにエンドポイントの検証を行うためのファイルを設置します。

// aibo クラウドから送られてきた検証用 HTTP リクエストを受け取る
$json = file_get_contents("php://input");
$contents = json_decode($json, true);

// エンドポイント登録
$challenge = $contents["challenge"];
echo '{"challenge": "' . $challenge . '"}';

次に、イベント通知のページにアクセスします。
ボタン「エンドポイント設定」をクリックして、エンドポイントとセキュリティートークンの設定を行います。

ss03.png

セキュリティートークンはあらかじめコピーしておきます。
通知イベント追加」で必要です。

ボタン「設定」をクリックしたら、エンドポイントの検証が開始します。
検証に成功したら、エンドポイントとセキュリティートークンの設定は完了です。

イベント追加

続いて、項目「通知するイベント」にある
ボタン「追加」をクリックして、イベントの追加を行います。

ss04.png

認識ワードで登録した音声コマンドをすべて登録できます。

ss05.png

アクセストークン取得

アクセストークンのページにアクセスします。

ss06.png

ボタン「生成する」をクリックすると、アクセストークンが生成されます。
アクセストークンはあらかじめコピーしておきましょう。
次項の「通知イベント追加」で必要になります。

通知イベント追加

いよいよ aibo にやってもらう処理を作っていきます。
まず最初にでき上がりのソースをペタッと貼り付けておきます。

const BASE_PATH = 'https://public.api.aibo.com/v1';
const ACCESS_TOKEN = ${Your Access Token};
const SECURITY_TOKEN = ${Your Security Token};

// ふるまいを実行
function execute_action ($device_id, $event_id) {
  if ( $event_id === 'voice_command::usercommand1' ) {
    // くしゃみして
    call_action_api($device_id, 'play_motion', '{"Category": "sneeze", "Mode": "NONE"}');
  } elseif ( $event_id === 'voice_command::usercommand2' ) {
    // おいしかった?
    call_action_api($device_id, 'play_motion', '{"Category": "friendly", "Mode": "NONE"}');
  } elseif ( $event_id === 'voice_command::usercommand3' ) {
    // きみはだんだんねむくなる
    call_action_api($device_id, 'play_motion', '{"Category": "dreaming", "Mode": "NONE"}');
  }
}

// Action API 呼び出し
function call_action_api ($device_id, $api_name, $arguments) {
  $post_url = BASE_PATH. '/devices/'. $device_id. '/capabilities/'. $api_name. '/execute';
  $data = '{"arguments":'. $arguments. '}';

  // POST API
  post_api($post_url, $data);
}

// POST API
function post_api ($url, $argary) {
  // HTTP設定
  $options = array (
    'http' => array (
        'method' => 'POST',
        'header' => 'Authorization:Bearer '. ACCESS_TOKEN,
        'content' => $argary,
        )
    );
  $contents = @file_get_contents($url, false, stream_context_create($options));

  // レスポンスステータス
  $status_code = http_response_code();
  if($status_code === 200) {
    // 200 success
  } elseif(preg_match ("/^4\d\d/", $status_code)) {
    // 4xx Client Error
    $contents = false;
  } elseif(preg_match ('/^5\d\d/', $status_code)) {
    // 5xx Server Error
    $contents = false;
  } else {
    $contents = false;
  }

  return $content;
}

// ヘッダーを取得
$headers = getallheaders();

// セキュリティートークンをチェック
if ( $headers['X-Security-Token'] != SECURITY_TOKEN ) {
  $response = '{"statusCode": 400}';
  return $response;
}

// 音声コマンドのリクエストを取得
$json = file_get_contents("php://input");
$contents = json_decode($json, true);
$status_code = http_response_code();

// 音声コマンドのリクエストが成功したら実行
if ($status_code === 200) {
  // 200 success
  $device_id = $contents['deviceId'];
  $event_id = $contents['eventId'];

  // ふるまいを実行
  execute_action($device_id, $event_id);
}

それぞれ見ていきます。

const BASE_PATH = 'https://public.api.aibo.com/v1';
const ACCESS_TOKEN = ${Your Access Token};
const SECURITY_TOKEN = ${Your Security Token};

${Your Access Token} には、取得したアクセストークンを
${Your Security Token} には、設定したセキュリティートークンを入れます。

// ヘッダーを取得
$headers = getallheaders();

// セキュリティートークンをチェック
if ( $headers['X-Security-Token'] != SECURITY_TOKEN ) {
  $response = '{"statusCode": 400}';
  return $response;
}

リクエストヘッダーで返ってきたキュリティトークンが正しい値であるか比較して
aibo Events API 以外からの不正なアクセスが行われるのを防ぎます。

// 音声コマンドのリクエストを取得
$json = file_get_contents("php://input");
$contents = json_decode($json, true);
$status_code = http_response_code();

// 音声コマンドのリクエストが成功したら実行
if ($status_code === 200) {
  // 200 success
  $device_id = $contents['deviceId'];
  $event_id = $contents['eventId'];

  // ふるまいを実行
  execute_action($device_id, $event_id);
}

音声コマンド → aibo → aibo クラウドより送られてきたリクエストを受け取り
json 形式で $contents に格納しています。

リクエストが成功したら、json から deviceId と eventId を取り出し
execute_action 関数の引数に渡して、実行しています。

// ふるまいを実行
function execute_action ($device_id, $event_id) {
  if ( $event_id === 'voice_command::usercommand1' ) {
    // くしゃみして
    call_action_api($device_id, 'play_motion', '{"Category": "sneeze", "Mode": "NONE"}');
  } elseif ( $event_id === 'voice_command::usercommand2' ) {
    // おいしかった?
    call_action_api($device_id, 'play_motion', '{"Category": "friendly", "Mode": "NONE"}');
  } elseif ( $event_id === 'voice_command::usercommand3' ) {
    // きみはだんだんねむくなる
    call_action_api($device_id, 'play_motion', '{"Category": "dreaming", "Mode": "NONE"}');
  }
}

execute_action 関数では、$event_id ごとに条件分岐を行い
aibo にしてもらう PlayMotion のふるまいを振り分けています。
PlayMotion だけでも 76 種類ものふるまいが用意されています。

// Action API 呼び出し
function call_action_api ($device_id, $api_name, $arguments) {
  $post_url = BASE_PATH. '/devices/'. $device_id. '/capabilities/'. $api_name. '/execute';
  $data = '{"arguments":'. $arguments. '}';

  // POST API
  post_api($post_url, $data);
}

call_action_api 関数では、POST する URL とコンテンツの生成を行い
post_api 関数の引数に渡して、実行しています。

// POST API
function post_api ($url, $argary) {
  // HTTP設定
  $options = array (
    'http' => array (
        'method' => 'POST',
        'header' => 'Authorization:Bearer '. ACCESS_TOKEN,
        'content' => $argary,
        )
    );
  $contents = @file_get_contents($url, false, stream_context_create($options));

  // レスポンスステータス
  $status_code = http_response_code();
  if($status_code === 200) {
    // 200 success
  } elseif(preg_match ("/^4\d\d/", $status_code)) {
    // 4xx Client Error
    $contents = false;
  } elseif(preg_match ('/^5\d\d/', $status_code)) {
    // 5xx Server Error
    $contents = false;
  } else {
    $contents = false;
  }

  return $content;
}

ヘッダーに Authorization:Bearer を追加して、コンテンツを POST します。
これで aibo に暗示をかけることができました。

実行

暗示をかけた aibo に言葉をかけてみましょう。
わん!と元気よく答えて、ふるまいを行ってくれたら成功です。

まとめ

いかがでしたでしょうか。

例えば、aibo Events API とスマートリモコンを連携すれば
ちょっとおバカ(褒め言葉)な aibo が我が家にある家電の司令塔になれちゃうわけです。

…素敵すぎませんか?

SONY さん神リリースありがとうございます。
欲を言えば、登録できる認識ワードを是非とも増やしていただきたいです!

さてさて… 我が家のおチャコさんを司令塔という重役につかせるべく
つい先日、Nature Remo 3 の予約をしてしまいましたよ。
はーやっくこないかな〜!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xampp(Apache+PHP)にてLocalネットワークからGmailが送信できるまで

背景

 完全に備忘録です。私のバイト先で、「ローカルのみで動いているサイトからGmailを送信したい」とお願いされたので、いろいろ調べたことをメモしておきます。

注意)
 私はサーバーとかセキュリティ初心者です。もしかしたらとても危険な操作をしているかもしれません。もしこの記事を参考にされる方いらっしゃいましたら、自己責任でお願いいたします。

環境

 動作環境は以下の通りです。この記事ではxamppのインストールから書いていこうと思います。また、Gmailアカウントについては携帯電話番号登録がされている場合(二重認証)の場合と、携帯番号無しでGmailアカウントを作った2通りの説明を残します。
(Gmailアカウントは持っている前提で話を進めます)

  • Windows10
  • Xampp(Apache + PHP + Mercury Mail Server + Fake Sendmail)
  • Gmail

xamppインストール編

 まず、以下のURLからxamppインストーラをダウンロードしてください。

そして、インストーラをダウンロード出来たら、以下の画像に従って設定してください。
やってることとしては、ApacheとPHPとMercury Mail Server、PHP、Fake Sendmailをインストールする感じです。

q1.PNG
q2.PNG
q3.PNG
q4.PNG
q5.PNG
q6.PNG
q7.PNG
q8.PNG
q9.PNG

そして、インストールが終わったらこんな画面(↓)が出てきます。これは閉じちゃってください。

q10.PNG

php.iniの変更

まず、エクスプローラから「C:\xampp\」直下の「xampp-control.exe」を管理者として実行してください。そして、ApacheとMercuryの「Start」ボタンを押して正常に動作する事を確認してください。もし動かないようであれば、ポート80が競合してしまっている可能性があるので以下の記事を参考にしてみてください。

q11.PNG

そして、Xamppコントローラの右側にある「Explorer」をクリックして、phpフォルダ内の「php.ini」をメモ帳から開きます。

q12.PNG
q13.PNG

開いたら、[mail function]のあたりまで移動します(F5とかで検索するとすぐ見つけられます)。そして、赤線の部分を以下の様に書き換えて、上書き保存してください。

・・・
[mail function]
・・・
sendmail_path="\"C:\xampp\sendmail\sendmail.exe\" -t"
・・・

q26.PNG

Gmailアカウントの設定

 原理的なお話をしますが、だいぶ憶測が含まれています。雰囲気としてみてください。正しい保証はできません。原理的な話は飛ばして結構です。自分用に書きます。

原理的なお話

 ここで、いったん原理的な説明をしましょう。ローカルPCのサーバーからGmailのメールを使うには、「Gmailのアカウント」と「外部からのアプリケーションの受け入れの設定」が必要です。
 Gmailのアカウントが必要な理由は、ローカルサーバーから誰かにメールを送信するとき、自身(送信元)のメールアカウントが必要なわけですが、ローカルPCだけではそれを持っていないので、事前にサーバーに紐づけられるGmailのアカウントを別に作っておく必要があるのです。
(図はあくまでイメージ図なので、厳密に正しくありません)

図1.png

 また、Googleメールでは、デフォルトで、見知らぬサーバーからの操作を受け付けない設定になっています。(これは、Gmailのメールアカウントとパスワードを知られていたとしても受け付けません)これを回避する為に、いくつかアカウント側で設定が必要です。

図2.png

 また、この設定は、Gmailの登録の段階でスマホなどの携帯電話番号を登録して二重認証(ログイン時とかにSMSなどに番号が送られてくる認証システム)している場合と、電話番号なしで作成した場合でアカウントの設定が異なってきます。

携帯電話番号を登録している場合(二重認証しているアカウント)

 おそらく、こちらの場合の方が多いと思います。まず、以下のURLからGoogleアカウントの設定をします。
(今回使用するメールアドレスのGoogleアカウントでログインしてください)

そして、「セキュリティ」タブの「アプリパスワード」をクリックします。(その後パスワードを求められますので入力してください)

q15.PNG

アプリは「メール」を選択して、

q16.PNG

デバイスは「その他」から「sendmail」を入力して、生成ボタンを押します。すると、「お使いのデバイスのアプリパスワード」が表示されるので、これをメモしておきます。「sendmail.iniの変更」で使うので覚えておいてください。

q17.PNG
q18.PNG

携帯電話番号を登録していない場合

 私はこの場合に当てはまるのですが、あまり多くないケースかもしれません。まず、以下のURLからGoogleアカウントの設定をします。
(今回使用するメールアドレスのGoogleアカウントでログインしてください)

そして、「セキュリティ」タブの「安全性の低いアプリのアクセス」の「アクセスを有効にする」をクリックしてください。

q20.PNG

ボタンを右にスライドさせて有効にさせたら設定終了です。

q21.PNG

sendmail.iniの変更

 Xamppコントロールパネルの「explorer」から、「sendmail」フォルダ内にある「sendmail.ini」をメモ帳から開きます。
q22.PNG

そして、以下の二か所を変更して、上書き保存してください。

・・・
[sendmail]
・・・
smtp_server=smtp.gmail.com
・・・
smtp_port=587
・・・

q23.PNG

最後に、以下の「auth_username」と「auth_password」を設定するのですが、ここでも携帯電話番号を登録しているアカウントとしていないアカウントで変わってきます。携帯電話番号を登録しているアカウントは、

auth_username=username@gmail.com
auth_password=app_pass

としてください。username@gmail.comは使用するGメールアカウントで、app_passはGoogleアカウントの設定で取得した「お使いのデバイスのアプリパスワード」の事です。(""とかはいらないです。そのまま書き込んでください。

 また、携帯電話番号を登録していないアカウントは、

auth_username=username@gmail.com
auth_password=password

としてください。username@gmail.comは使用するGメールアカウントで、passwordはログイン時に使う、アカウントのパスワードです。

q24.PNG

以上で、設定の方は終わりです。

テスト

 それでは、メール送信ができるかどうかテストします。最初に、XamppコントロールパネルからApacheを再起動します。「Stop」ボタンを押して「Start」ボタンを押せば再起動できます。
q25.PNG

 そして、「C:\xampp\htdocs」直下に以下のソースコードを「mail.php」の名前で保存してください。username@gmail.comは設定の際に使用したGmailアカウントです。
 最後に、「localhost\mail.php」とChromeとかで検索してみてください。

<?php

$to      = 'username@gmail.com';
$subject = 'sample';
$message = 'success!!';
$headers = 'From: username@gmail.com' . "\r\n";

if(mail($to, $subject, $message, $headers)){
    echo "メール送信は成功しました!!!";
}else{
    echo "残念ながらメールは送信できませんでした。。。";
}
?>

以下の様に表示されれば送信が成功していますので、Gmailアカウントまで行って、メールが送信されているのを確認してください。

q27.PNG

最後に

 今回は、Localで動いているウェブサイトからGmailを送信するための手順をまとめてみました。どうやらWindowsではSMTPメールサーバが元から入っていないため、Apache単品+PHP単品ではメール送信ができないみたいです。色々調べましたが、結局Xamppに任せた方が簡単だと思います。
 最後に、しつこいですが、この記事を参考にされる場合は自己責任でお願いしますm(_ _)m
また、何かアドバイス・修正などがあれば、コメントか編集リクエストを頂けたら大変ありがたいです!

それでは!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでRedisコンテナを立ち上げLaravelで読み書きする

はじめに

DockerでRedisコンテナを立ち上げLaravelから読み書きしてみましたので、
Redisコンテナを立ち上げるまでの最小限の構成を紹介します。

やりたいこと

  • DockerでRedisコンテナを立ち上げたい
  • Redisのデータを永続化したい
  • LaravelからRedisのデータを読み書きしたい

この記事で説明しないこと

  • Redis以外のDocker構成
  • Laravelのインストール手順

ディレクトリ構成

.
├── data
│   └── redis
│       └── appendonly.aof(コンテナ起動時に作成されます)
├── docker-file
│   ├── nginx
│   │   └── default.conf
│   └── php
│       ├── Dockerfile
│       └── php.ini
├── volumes
│   └── www
└── docker-compose.yml

各ファイルの内容

docker-compose.yml

データ永続化の為にdataフォルダをマウントしています。

また、redisコンテナを立ち上げ直すたびにIPアドレスが変わっては不便なのでnetworksで固定IPを割り振るようにしています。

docker-compose.yml
version: '3.3'

services:
  php:
    container_name: php
    build: ./docker-file/php
    volumes:
      - ./volumes/www:/var/www
    networks:
      app_net:
        ipv4_address: 172.16.238.2

  nginx:
    image: nginx
    container_name: nginx
    ports:
      - 8080:80
    volumes:
      - ./volumes/www:/var/www
      - ./docker-file/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php
    networks:
      app_net:
        ipv4_address: 172.16.238.3

  redis:
    image: "redis:latest"
    container_name: redis
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes
    volumes:
      - "./data/redis:/data"
    networks:
      app_net:
        ipv4_address: 172.16.238.4

networks:
  app_net:
    driver: bridge
    ipam:
     driver: default
     config:
       - subnet: 172.16.238.0/24

docker-file/nginx/default.conf

default.conf
server {
  listen 80;

  root  /var/www/public;
  index index.php;

  location / {
    try_files $uri $uri/ /index.php$is_args$args;
  }

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass   php:9000;
    fastcgi_index  index.php;

    include        fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param  PATH_INFO $fastcgi_path_info;
  }
}

docker-file/php/Dockerfile

公式でPhpRedis PHP拡張が推奨されている為、phpredisをgit cloneしています。

LaravelでRedis使用するには、PECLを使用してPhpRedis PHP拡張をインストールすることを推奨します。インストール方法は複雑ですが、Redisをヘビーユースするアプリケーションではより良いパフォーマンスが得られます

FROM php:7.3-fpm
COPY php.ini /usr/local/etc/php/

RUN apt-get update \
  && apt-get install -y zlib1g-dev libzip-dev libpq-dev iputils-ping net-tools git
RUN git clone https://github.com/phpredis/phpredis.git /usr/src/php/ext/redis
RUN docker-php-ext-install zip pgsql pdo_pgsql redis

COPY --from=composer /usr/bin/composer /usr/bin/composer

ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin

WORKDIR /var/www

RUN composer global require "laravel/installer"

docker-file/php/php.ini

php.ini
[Date]
date.timezone = "Asia/Tokyo"

[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

実行

docker-composeで起動します。

$ docker-compose up -d
Creating network "redis-docker_app_net" with driver "bridge"
Creating php   ... done
Creating redis ... done
Creating nginx ... done

Laravelから読み書きする

config/database.phpを設定する必要がありますが、基本的には初期設定のままで問題ありません。
参考:Redis 6.x Laravel

書き込み、読み込み

/app/Http/Controllers/RedisController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Redis;

class RedisController extends Controller
{
    /**
     * Redisへデータをセット
     */
    public function setRedis($id)
    {
        Redis::set('test', $id);

        return;
    }

    /**
     * Redisのデータを取得
     */
    public function getRedis()
    {
        $redis_data = Redis::get('test');

        return;
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP7.2 × DockerでgRPCクライアントを使うための環境構築

Goでクライアントを作るときには、brewgo getでパッケージをいくつか入れるだけで済むのですが、PHPの方ではいくつかハマりどころがあったのでまとめました。

前提

  • protoコマンドがインストールされている
  • 使用するRPC方式はUnaryRPCである
  • コード生成はローカル環境で行う

扱うprotoファイル

下記のprotoに少し変更をかけたものを扱います。
https://grpc.io/docs/languages/php/quickstart/

hello.proto
package hello;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

コード生成

下記ページを参考にしました。
https://grpc.io/docs/languages/php/quickstart/

コード生成用のPHPプラグインを追加

protoファイルからPHP用の各種クラスを生成するために、grpc_php_plugin を入れる必要があります。

$ git clone -b v1.30.0 https://github.com/grpc/grpc
$ cd grpc && git submodule update --init && make grpc_php_plugin

すると、ソースを展開したディレクトリに bins/opt/grpc_php_plugin が生成されます。

コード生成実行

下記コマンドを入力します。

$ protoc --proto_path=proto \
  --php_out=src/gen \
  --grpc_out=src/gen \
  --plugin=protoc-gen-grpc=./grpc/bins/opt/grpc_php_plugin \
  ./proto/hello.proto

この時のオプションとして、

  • --proto_path にはprotoファイルが配置されているディレクトリを指定する
  • --php_out--grpc_out は同じディレクトリを指定する
  • --plugin=protoc-gen-grpc= に、先ほどプラグインをインストールしたディレクトリの bins/opt/grpc_php_plugin を指定する

を行います。
これでコード生成はOKです。

クライアント環境準備

インストールするパッケージは下記ページを参考にしました。
https://cloud.google.com/php/grpc?hl=ja#php-implementation

Dockerの上で動かしたPHPで、gRPCのリクエストを送るための環境を構築します。

php.ini

grpc.so 拡張パッケージの記述がポイントです。

php.ini
[Date]
date.timezone = "Asia/Tokyo"

[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

extension = grpc.so

Dockerfile

peclでgrpcパッケージを入れることがポイントです。

FROM php:7.2-stretch

RUN apt-get update -y \
    && apt-get install -y zip autoconf zlib1g-dev \
    && apt-get clean

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
COPY php.ini /usr/local/etc/php/php.ini
RUN pecl install grpc

WORKDIR /var/local
COPY . .
RUN composer install

composer.json

ここでは先ほどのコード生成によってファイルが格納された先を src/gen として扱っています。

composer.json
{
    "autoload": {
        "psr-4": {
            "Hello\\": "src/gen/Hello",
            "GPBMetadata\\": "src/gen/GPBMetadata"
        }
    },
    "require": {
        "php": ">=7.2",
        "google/protobuf": ">=3.0",
        "grpc/grpc": ">=v1.27.0"
    },
    "repositories": [
        {
          "type": "vcs",
          "url": "https://github.com/stanley-cheung/Protobuf-PHP"
        }
    ]
}

ポイントは、

  • google/protobuf パッケージのインストール
  • grpc/grpc パッケージのインストール
  • コード生成されたクラスをautoloadで読み込み指定する

ことです。

docker-compose.yaml

先ほどのphp.iniを、ボリュームマウントでコンテナに配置することがポイントです。

docker-compose.yaml
version: "3"
services:
  php:
    build: .
    volumes:
      - ./php.ini:/usr/local/etc/php/php.ini
      - .:/var/local/
    ports:
      - 80:80

クライアント実装

実装例です。

srcipts/RunHello.php
<?php

use Hello\GreeterClient;
use Hello\HelloRequest;

$hostname = 'localhost:8000';
$client = GreeterClient($hostname, [
    'credentials' => \Grpc\ChannelCredentials::createInsecure()
]);

$request = new HelloRequest();
$request->setName("DummyName");
list($reply, $status) = $client->SayHello($request)->wait();
$message = $reply->getMessage();

この時vendor配下に \Grpc\ChannelCredentials クラスが存在せず、エディタでは警告等が出ます。
しかしPHPのgRPC拡張によって、必要なライブラリがDocker側にインストールされているため実行時には正常に通ります。
(個人的にはここがハマりポイントでした)

実行

オートローダーを読み込んで、クライアント実装のスクリプトを読み込めば実行されます。

$ docker-compose run --rm php -a
php> require_once('vendor/autoload.php');
php> require_once('scripts/RunHello.php');

あとはこのクライアントをクラスに閉じ込めたり、composerのパッケージ化などを行えばWebサーバー等にも組み込むことができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】シーディング

内容

シーディングについてメモしておきます。

factoryファイルの作成

UserFactoryはデフォルトで作成されています。
ここではuser_idを外部キーにもつPostモデルのfactoryファイルを作成してみます。

% php artisan make:factory PostFactory --model=Post

--modelオプションでモデルを指定しています。
ファイルの中身は、例えば以下のようにします。

database/factories/PostFactory.php
use Illuminate\Support\Str;

$factory->define(Post::class, function (Faker $faker) {
    return [
        'title' => $faker->title(),
        'comment' => Str::random(10),
    ];
});

指定していないcreated_atやupdated_atなどは自動生成してくれるようです。
user_idは下の方で説明する書き方をすれば自動に値を入れてくれます。

seederファイルの作成

% php artisan make:seeder UserSeeder
database/seeds/UserSeeder.php
    public function run()
    {
        for ($i = 0; $i < 50; $i++) {
            $user = factory(User::class)->create(['name' => 'user'.$i]);
            $posts = [];
            for($j = $i * 10; $j < $i * 10 + 10; $j++) {
                array_push($posts, factory(Post::class)->make([
                    'title' => 'title'.$j,
                    'comment' => 'comment'.$j,
                ])->toArray());
            }
            $user->posts()->createMany($posts);
        }
    }

見慣れない記述ですが、titleとcommentに連番をつけたかったので、上のような書き方をしました。

factory(User::class)->create();
で一つのデータが作成されます。create()の引数を指定することで、値をオーバーライドできます。
1対多のリレーションでは、createMany()を利用するといいでしょう。引数は配列にする必要があります。

DatabaseSeeder.php

database/seeds/DatabaseSeeder.php
    public function run()
    {
        $this->call([
            UserSeeder::class,
        ]);
    }

run()内のcallメソッドの引数に実行するSeederを指定します。

オートローダ

Seederファイルを作成したら、以下のコマンドを実行して、オートローダを再生成しましょう。

% composer dump-autoload

シーディング

% php artisan db:seed
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでPHP7.4のLaravel開発環境を作成

PHP7.4のLaravel開発環境を、Docker-Composeで構築して、Laravelの新規プロジェクトを作成して起動してみるところまで確認してみます。
データベースには「MariaDB」を使います。

環境

この記事を書くにあたって使用した環境です。どれも、2020年7月時点の最新バージョンを使用しているハズ。

Docker

Name Version
docker 19.03.1
docker-compose 1.24.1

Web

今回は、Webサーバーに「Apache」を使用します。

Name Version
PHP 7.4
Apache 2.4.38
Laravel 7.21.0

DB

データベースは「MariaDB」です。データベース管理ツールの「Adminer」も入れておきます。

Name Version
MariaDB 10.5.4
Adminer 4.7.7

フォルダ構成

今回は、次のようなフォルダ構成にします。

[Project_ROOT]
├ docker
│ └ web
│   ├ apache
│   │ └ 000-default.conf
│   ├ php
│   │ └ php.ini
│   └ Dockerfile  
├ mysql
│ ├ data
│ └ initdb
└ docker-compose.yml

docker-compose.yml

version : "3"
services:
  web:
    container_name: web
    build: ./docker/web
    ports:
      - 80:80
    privileged: true
    volumes:
      - ./server:/var/www/html

  db:
    image: mariadb
    restart: always
    ports:
      - 3307:3306
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/initdb:/docker-entrypoint-initdb.d
    environment:
      - MYSQL_ROOT_PASSWORD=pwd
      - MYSQL_DATABASE=test
      - MYSQL_USER=user
      - MYSQL_PASSWORD=pwd

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

Dockerfile (PHP+Apache)

docker/webにwebコンテナ 構築用の「Dockerfile」を作成します。
PHP+Apacheのイメージをベースに、各種ライブラリとComposer、Laravelをインストールします。

FROM php:7.4-apache
COPY ./php/php.ini /usr/local/etc/php/
COPY ./apache/*.conf /etc/apache2/sites-enabled/

RUN apt-get update \
  && apt-get install -y zlib1g-dev libpq-dev mariadb-client unzip libzip-dev\
  && docker-php-ext-install zip pdo_mysql mysqli \
  && docker-php-ext-enable mysqli

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin

WORKDIR /var/www/html

RUN composer global require "laravel/installer"

※ 以前のバージョンでは不要でしたが、今回インストールするcomposerには「libzip-dev」が必要なので一緒にインストールします。

php.ini

PHPの設定ファイルを作成します。

[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

Apacheの設定ファイル

Apacheのポートや、ドキュメントルートなど、最低限の設定を行います。

<VirtualHost *:80>
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html/public
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Dockerコンテナの起動

各種ファイルの準備ができたら、次のコマンドを使用してDockerコンテナを起動します。最初は400MB近いファイルがダウンロードされ時間がかかるので、気長に待ちます。

$ docker-compose up -d

Laravelプロジェクトの作成

webのコンテナ にシェルでログインして、Laravelの新規プロジェクトを作成します。

# webコンテナにシェルで入ります
$ docker-compose exec web bash

# 新規のLaravelプロジェクト作成します
$ laravel new

動作確認

ここまでで、WEBとDBのコンテナの作成と、Laravel新規プロジェクトの作成は完了です。作成したコンテナが正常に動作しているか確認していきましょう。

ブラウザでhttp://127.0.0.1/にアクセスして、Laravelプロジェクトが正常に動作しているか確認します。
スクリーンショット 2020-07-24 13.50.42.png

次に、データベース(MariaDB)とAdminerが動作しているか確認します。
ブラウザでhttp://127.0.0.1:8080/にアクセスし、docker-composeで指定したパスワードでログインします。
スクリーンショット 2020-07-24 14.10.11.png

ログインに成功すると、次のような画面が表示されます。
スクリーンショット 2020-07-24 19.10.19.png

まとめ

PHP7.4のLaravel開発環境を作ってみました。デバッグ実行はできませんが、dockerを使えば簡単にPHP+Laravelの環境が作れて便利です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GulpのBrowserSyncとPHPのビルトインサーバを組み合わせる

要約

コーディングでBrowserSyncを使うときにPHPも使いたいけど、
さくっとビルトインサーバで実行するのに詰まったのでメモ。
要はBrowserSyncのproxyを使うのと、ビルトインサーバを0.0.0.0で実行したらいけた。

ビルトインサーバの起動

localhost:8000でもいいかと思ったけどダメだった。
のでlocalhostじゃなくても参照できるよう起動。

# 先にビルトインサーバ起動。localhostだとproxyが効かない?(効かなかった)
php -S 0.0.0.0:8000

BrowserSyncの方はproxyでビルトインサーバを参照するだけ。

gulpfile.js
// これでlocalhost:3000のリクエストはlocalhost:8000を参照する
browserSync: {
  proxy: 'localhost:8000'
}

その他、htmlファイルでPHPを実行したいのもあったので、細かく調整。
ApacheでいうところのAddTypeに相当する実行をするためにrouter.phpを用意して、

router.php
// .htmlをPHPファイルとして実行する設定
// .htaccess の AddType application/x-httpd-php .html に相当
$path = $_SERVER["SCRIPT_FILENAME"];
if (preg_match("/\.html$/", $path)) {
    chdir(dirname($path));
    return require($path);
}

return false;

ビルトインサーバを下記で起動すればOK

php -S 0.0.0.0:8000 ./router.php

これでfor文とかで項目水増し()も楽チン♪(pugを覚える努力を惜しんだ)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Composerを使った環境構築

Composerを使ってLaravelをローカル環境に構築する。

terminalでapplications/mamp/htdocsに移動する。
composerのcreate-projectコマンドを使ってLarabelをインストールする。

composer create-project --prefer-dist laravel/laravel 【ディレクトリ名】

※【ディレクトリ名】の部分がフォルダの名前になる。
Laravelのインストール

バージョン

php:7.3.11
Laravel:7.21.0

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】関数

書籍のアウトプットとして

構文の表記について

                       仮引数
  戻り値のデータ        ┌─┴─────────────┐
void  asort ( array $array [, int $sort_flags = SORT_REGULAR])        
  関数名─┘       └─引数のデータ型─┘                       └─デフォルト値

[](ブラケット)で囲まれた引数は省略可能

データ型

名前 説明
mixed 複数のデータを返す可能性がある(戻り値の場合)、または複数のデータ型を指定できる(引数の場合)
void 戻り地がない

文字列関数

mb_strlen

文字列の長さを取得する
strlenとの違いはマルチバイトに対応しているかしていないか。

mb_substr

文字列から特定文字列を抽出
引数には負の数も指定できるがこれにメリットは有るのか?

printf

int printf(string $format [, mixed $args [,...]])

プレイスホルダ(変換指定子)に含めることができる指定子

符号指定子

数値に付与する符号を指定する

$num = 120;
$num2 = -120;
printf("数値は%+dです\n", $num);//数値は+120です
printf("数値は%-dです\n", $num);//数値は120です
printf("数値は+%dです\n", $num2);//数値は+-120です
printf("数値は-%dです\n", $num2);//数値は--120です

負数はデフォルトで−がつく。
正数で+を指定すると強制的に+がつく。

パディング指定子

不足している桁を埋めるための文字(デフォルトは空白)
0を指定すると0詰め。

$num = 120;
printf("数値は%05dです", $num);//数値は00120です

0以外も指定でき、その際は'(シングルクォーテーション)を文字の前につける

$num = 120;
printf("数値は%'_5dです", $num);//数値は__120です

アラインメント指定子

文字列を寄せる。デフォルトは右寄せ
-指定して左寄せ。

$num = 120;
printf("数値は%-5dです", $num);//数値は120  です

表示幅指定子

文字列を最低何桁にするかを指定する。

$num = 120;
printf("数値は%5dです", $num);//数値は  120です

精度指定子

浮動小数点数にたいして小数点以下の表示桁を指定

$num = 120;
printf("数値は%.5fです", $num);//数値は120.00000です

型指定

名前 引数 表示
b 整数 2進数
c 整数 ASCII
d 整数 10進数
e,E 指数表記として処理
f double 浮動小数点数
F float 浮動小数点数
o 整数 8進数
s 文字列
x 整数 16進数(小文字)
X 整数 16進数(大文字)

配列関数

array_push/array_unshift/array_pop/array_shift

配列に値を追加できる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【学習記録(PHP)】関数作りの失敗

こんにちは、いちかわです。・ V ・

ECサイトを制作する過程で、関数の取り扱いが増えてきたので、
忘れないように記録させていただきます。

関数は便利で分かりやすくが基本!

関数をつくるとき、修正のしやすさや、流用が効く書き方が重要になってきます。

まずはこちらをご覧ください。関数を覚えたての私が、最初に書いた関数です。
おそろしい見た目をしていますが、このときの私は「ちゃんと動いた!」と、とても満足していました。

こちらのプログラムは、\$nameと\$commentの文字数をカウントし、指定文字数をオーバーした場合には、\$err_msgという配列にエラーメッセージをためるという仕様になっています。

function.php
$err_msg = check_text_length($err_msg, $name, $comment); 
function.php
function check_text_length($err_msg, $name, $comment) {
    $name_length = mb_strlen($name);      
    $comment_length = mb_strlen($comment);          

    if ($name_length > 20 || $comment_length > 100) {     
        $err_msg[] = '注)名前は20字以内、コメントは100字以内におさめてください。';  
    }
    return $err_msg;
} 



こちらの関数のいけないところは、


  • 「check_text_length」という命名が分かりにくく、何が返ってくるかが分からない。

  • それぞれの引数に何を入れたらよいかが分からない。

  • 限定的で、他のプログラムへの流用が効かない。


などなど、上げだしたらきりがありません(TT)


そこで、この怖い関数を自分なりに見れる関数へ改良してみました。
それがこちらになります。
function.php
if (is_over_length($name, 20) === true) {
    $err_msg[] = '注)名前は20字以内におさめてください。';
}

if (is_over_length($comment, 100) === true) {
    $err_msg[] = '注)コメントは100字以内におさめてください。';
}  
function.php
function is_over_length($str, $int) {
    $str_length = mb_strlen($str);
    if ($str_length > $int) {
        return true;
    }
}

まずは、曖昧だった「check_text_length」という関数名を、「is_over_length」に変更して、真偽値が返ってきそうな雰囲気を出しました。
また、第1引数は文字数をチェックしたい変数、第2引数は文字数の上限値とすることで、関数の分かりやすさと、汎用性を改善しました。
こうすることで、先ほどの関数に比べて見栄えも使いやすさも改善されました。
もしも、「こうすればもっとよくなるよ」というご指摘がございましたら、教えていただけますと幸いです。


勉強を進めれば進めるほど、分からないことはどんどん増えていく一方ですが、
過去の自分のプログラムの書き方を指摘できるようになったのは、ちゃんと前に進んでいけてるからだと思います。昔の自分の姿を見るというのは少し恥ずかしいのですが、今後も成長を実感しながら勉強していきたいと思います。


ここまでご覧頂き、ありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】Phan で "PhanUnreferencedUseNormal Possibly zero references to use statement for classlike/namespace"

Phan で静的コード解析すると「PhanUnreferencedUseNormal Possibly zero references to use statement for classlike/namespace MyClass」エラーが出る。Symfony の Component 利用時にも出る。

Phanのエラー
PhanUnreferencedUseNormal Possibly zero references to use statement for classlike/namespace MyClass (\MyVendor\MyPackage\MyNameSpace\MyClass)

「phan PhanUnreferencedUseNormal Possibly zero references to use statement for classlike/namespace」でググっても「何か違う」記事や Issue しか出てこなかったので、自分のググラビリティとして。

TL; DR

「ソースコード冒頭の use ステートメント(声明)で宣言されるも、そのファイル内で使われている形跡がなさげ」という意味です。

該当する use 文のクラス/オブジェクトのインスタンスが、そのファイル中で本当に使われているか確認してください。

TS; DR

「この PHP がテンプレートエンジンのくせに慎重すぎる」シリーズに啓示を受けて、プロジェクトの開始時から静的解析ツールを使うようになりました。

どの静的解析ツールも基本中の基本は同じ確認をしてくれます。しかし、各々に解析する目線が異なっており、片方ではエラーが出なくても、もう片方でエラーが出ることが多々あります。口うるさく感じるものの、リファレンスを読むとそれなりの理由があり、思い込みや決めつけが強くポカが多い自分にはいいポカヨケになっています。

そんな中、とある俺様パッケージ開発中に、ユニットテストをパスしたのでいよいよ解析ツールからのエラー潰し込みに入ったものの、表題のエラーで予想外の時間を消費してしまいました。

ネットで調べても「Symfony 形式コメント対応 Phan プラグインを入れろ」だの、Symfony のコメント記法は独特だから解析できないなど、何か求めている情報とは違うようです。盲目的に試してみるも、効果なし。

一晩置いたら、エラー・メッセージに理由が書いてありました。。。

  • PhanUnreferencedUseNormal
    • 「Phan です。use が普通に参照されていませんエラー」という意味。
  • Possibly zero references to use statement for classlike/namespace MyClass
    • /クラス風の/俺様名前空間/MyClass のための use 声明文に対して参照ナッシングの可能性あり」という意味。

落ち着いてエラー内容を読めばその通りなのですが、静的解析のエラーを1つ1つ潰していたら、何か麻痺していたようです。叱られる時に「あの時も。。。」と連続して色々注意されると、結局何を注意されていたのかわからずトンチンカンな行動を取るのと似ています。とほほ。。。

このエラーは、実際には俺様クラスだけでなく、Symfony の HttpClient コンポーネントでも出ていました。きっかけは、本体クラスから汎用メソッドを Trait 化した時です。

メソッドの引数でクラス名で型宣言する場合、use 文で利用するクラスを宣言しておかないと、静的解析ツールから叱られます。

SampleTrait.php
<?php

declare(strict_types=1);

namespace \MyVendor\MyPackage\MyNameSpace;

use \Symfony\Component\HttpClient\HttpClient;

trait Sample
{
    public function requestUrlAsGet(HttpClient $client_http, string $url_target): array
    {
        $response = $client_http->request('GET', $url_target);
        return $response->toArray();
    }
}

実際には Trait を利用する先で use 宣言していればスクリプトは動くことは動きます。

しかし、ダイレクトに Trait ファイルを見た時に「なんじゃ、この降って湧いたようなクラスは」とならないように「このファイルで使うクラスを予め言っておくよ」と宣言しておけということです。

問題は、使っていないくせに「使うよ」と宣言されると、「どこ?どこ?」となってしまうので、無駄な宣言に対しても厳しいということです。

未来の俺よ、またここに来たらまずはもちつけ。落ち着いてエラーを読めばわかる。エラーの内容を読んで理解する前にググるな。orz

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】Phan の "PhanUnreferencedUseNormal Possibly zero references to use statement for classlike/namespace" エラー時注意点

Phan で静的コード解析すると「PhanUnreferencedUseNormal Possibly zero references to use statement for classlike/namespace MyClass」エラーが出る。Symfony の Component 利用時にも出る。

Phanのエラー
PhanUnreferencedUseNormal Possibly zero references to use statement for classlike/namespace MyClass (\MyVendor\MyPackage\MyNameSpace\MyClass)

「phan PhanUnreferencedUseNormal Possibly zero references to use statement for classlike/namespace」でググっても「何か違う」記事や Issue しか出てこなかったので、自分のググラビリティとして。

TL; DR

「ソースコード冒頭の use ステートメント(声明)で宣言されるも、そのファイル内で使われている形跡がなさげ」という意味です。

該当する use 文のクラス/オブジェクトのインスタンスが、そのファイル中で本当に使われているか確認してください。

TS; DR

「この PHP がテンプレートエンジンのくせに慎重すぎる」シリーズに啓示を受けて、プロジェクトの開始時から静的解析ツールを使うようになりました。

どの静的解析ツールも基本中の基本は同じ確認をしてくれます。しかし、各々に解析する目線が異なっており、片方ではエラーが出なくても、もう片方でエラーが出ることが多々あります。口うるさく感じるものの、リファレンスを読むとそれなりの理由があり、思い込みや決めつけが強くポカが多い自分にはいいポカヨケになっています。

そんな中、とある俺様パッケージ開発中に、ユニットテストをパスしたのでいよいよ解析ツールからのエラー潰し込みに入ったものの、表題のエラーで予想外の時間を消費してしまいました。

ネットで調べても「Symfony 形式コメント対応 Phan プラグインを入れろ」だの、Symfony のコメント記法は独特だから解析できないなど、何か求めている情報とは違うようです。盲目的に試してみるも、効果なし。

一晩置いたら、エラー・メッセージに理由が書いてありました。。。

  • PhanUnreferencedUseNormal
    • 「Phan です。use が普通に参照されていませんエラー」という意味。
  • Possibly zero references to use statement for classlike/namespace MyClass
    • /クラス風の/俺様名前空間/MyClass のための use 声明文に対して参照ナッシングの可能性あり」という意味。

落ち着いてエラー内容を読めばその通りなのですが、静的解析のエラーを1つ1つ潰していたら、何か麻痺していたようです。叱られる時に「あの時も。。。」と連続して色々注意されると、結局何を注意されていたのかわからずトンチンカンな行動を取るのと似ています。とほほ。。。

このエラーは、実際には俺様クラスだけでなく、Symfony の HttpClient コンポーネントでも出ていました。きっかけは、本体クラスから汎用メソッドを Trait 化した時です。

メソッドの引数でクラス名で型宣言する場合、use 文で利用するクラスを宣言しておかないと、静的解析ツールから叱られます。

SampleTrait.php
<?php

declare(strict_types=1);

namespace \MyVendor\MyPackage\MyNameSpace;

use \Symfony\Component\HttpClient\HttpClient;

trait Sample
{
    public function requestUrlAsGet(HttpClient $client_http, string $url_target): array
    {
        $response = $client_http->request('GET', $url_target);
        return $response->toArray();
    }
}

実際には Trait を利用する先で use 宣言していればスクリプトは動くことは動きます。

しかし、ダイレクトに Trait ファイルを見た時に「なんじゃ、この降って湧いたようなクラスは」とならないように「このファイルで使うクラスを予め言っておくよ」と宣言しておけということです。

問題は、使っていないくせに「使うよ」と宣言されると、「どこ?どこ?」となってしまうので、無駄な宣言に対しても厳しいということです。

未来の俺よ、またここに来たらまずはもちつけ。落ち着いてエラーを読めばわかる。エラーの内容を読んで理解する前にググるな。orz

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP calコマンドっぽいカレンダーを1行で

とりあえず、直にcalコマンドを実行する方向は無しで頑張ってみました。

cal.php
<?php
echo str_pad(($d = new DateTime())->setDate($argv[2] ?? $d->format('Y'), $argv[1] ?? $d->format('m'), 1)->format('F Y'), 20, ' ', 2). "\nSu Mo Tu We Th Fr Sa\n". preg_replace('/(.{1,21})/', "$1\n", str_repeat('   ', $d->format('w')). sprintf(str_repeat('%2d ', $l = $d->format('t')), ...range(1, $l)));
$ php cal.php
     July 2020
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
$ php cal.php 2
   February 2020
Su Mo Tu We Th Fr Sa
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
$ php cal.php 2 1999
   February 1999
Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28

まとめて1年分表示など色々再現できていませんが…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPのtry ~ catchで、Fatalエラーをcatchしたい場合は、ExceptionでなくThrowableを利用する

環境

PHP 7.4.0 CLI

Throwableは、PHP5系では利用できないとのこと

発生した問題

稼働しているアプリのログにたまにFatalエラーが記録されている。
原因を探るため、Fatalエラーが発生した場合にtry~catchでデバッグ用の処理を実行し、怪しい変数をvar_dump()してエラーログに吐きたい。
try~catchでエラーハンドリングするようにしたが、以前同じFatalエラーが出力され、エラーハンドリングできていないようにみえる。

解決策

\Exceptionではなく、\Throwableを利用する。
https://stackoverflow.com/questions/12928487/php-try-catch-and-fatal-error
https://www.php.net/manual/en/class.throwable.php

エラーは基本的に握りつぶさず、修正すべき

Exception、Fatalエラーを全部補足できる\Throwableは便利だと感じましたが、

Error はアプリケーションのロジック中で発生させたり、捕捉したりしてはいけません。これは、言語上とても基本的な何かが間違っているという問題をプログラマーに教えるためにあります。なので、迂闊な例外処理に紛れてしまわないようになっています。
https://qiita.com/tanakahisateru/items/e3e24f3825c4ba0c60e6#error

とあるように、常用するものではないようです。

関連記事

https://qiita.com/hnw/items/4e2d47d269a26025a726
https://qiita.com/jonsumisu/items/54a95d848f2dcf9cfde4
https://qiita.com/mpyw/items/c69da9589e72ceac470c
https://qiita.com/tanakahisateru/items/e3e24f3825c4ba0c60e6

あとがき

今回、cronでTwitterAPIを1分ごとに叩く処理だったんですが、RateLimitに引っかかった際のレスポンスを想定していなかったため、数十分に一度エラーが発生するという状況になっていました。
Throwableでエラーをcatchしてレスポンスの内容をvar_dump()することで、レスポンスの内容を確認することができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む