PHP 8.0 の設定ファイル php.ini-development と php.ini-production の違い

この記事は個人ブログと同じ内容です

www.ritolab.com


PHP には php.ini という設定ファイルがありますが、これを作成する元となる設定ファイルには本番用と開発用の2つが存在しています。

今回はこの2つの php.ini の違いを見ていきたいと思います。

環境

PHP 8.0 の環境で見ていきます。

$ php  -v
PHP 8.0.0 (cli) (built: Dec 17 2020 08:54:56) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies

php.ini

php.ini は PHP の設定ファイルの事で、PHP の動作に関する様々な設定をここに記述することができます。

設定ファイル
https://www.php.net/manual/ja/configuration.file.php

php.ini がどこに設置されているのかは以下のコマンドを叩くと確認する事ができます。(環境によって設置箇所は異なります)

# php.ini の場所を確認する
php --ini

# 実行結果
$ php --ini
Configuration File (php.ini) Path: /usr/local/etc/php
Loaded Configuration File:         /usr/local/etc/php/php.ini
Scan for additional .ini files in: /usr/local/etc/php/conf.d

本番用と開発用の php.ini

php.ini が設置されているディレクトリに php.ini-development と php.ini-production が存在しています。

/usr/local/etc/php
├── conf.d
├── php.ini
├── php.ini-development
└── php.ini-production

この2つのファイルを基に php.ini を作成するわけですが、これらのファイルについて、具体的にどこの設定項目がどう違うのでしょうか。

その違いを見ていきます。

余談ですが php.ini が無い場合はその項目ごとにデフォルト値が決まっていて、デフォルト値が適用されます

php.ini-development と php.ini-production の違い

2つのファイルの差分を取り、設定の違いを確認していきます。

f:id:ro9rito:20201228080006p:plain

zend.exception_ignore_args

例外用に生成されたスタックトレースに引数を含めるかどうかの設定。

  • 本番環境ではこの設定をオンにしてスタックトレースで機密情報を出力しないようにすることが推奨されている。

  • デフォルト:Off
  • 開発用:Off
  • 本番用:On

-zend.exception_ignore_args = Off
+zend.exception_ignore_args = On

開発用では引数を出力するようになっているが、本播用では出力しない設定になっている

zend.exception_ignore_args
https://www.php.net/manual/ja/ini.core.php#ini.zend.exception-ignore-args

zend.exception_string_param_max_len

文字列化されたスタックトレースの引数の最大文字列長を 0 から 1000000 までの範囲で値を設定できる。

  • zend.exception_ignore_args が有効になっている場合、ここは無視される。
  • 本番環境ではこれを 0 に設定してスタックトレースでの機密情報の出力を減らすことが推奨されている。

  • デフォルト:15
  • 開発用:15
  • 本番用:0

-zend.exception_string_param_max_len = 15
+zend.exception_string_param_max_len = 0

開発用では最大文字列長が 15 文字になっていて、本番用では 0 になっている。 (本番用は zend.exception_ignore_args = On なのでそもそも使われない)

ちなみにこれは PHP 8.0 で新たに追加されたディレクティブです。(RFC

error_reporting

エラー出力レベルを設定できる。

-error_reporting = E_ALL
+error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

開発用では全てのエラーと警告を出力するようにしているが、本番用では E_DEPRECATED と E_STRICT 以外のエラーや警告が出力されるように設定されている。

エラー出力レベルについては以下を参照ください

www.ritolab.com

error_reporting

https://www.php.net/manual/ja/errorfunc.configuration.php#ini.error-reporting

display_errors

PHP がエラーや通知、警告を表示するかどうかを設定する。

  • エラー表示は開発時には役に立つが、本番環境では機密情報が漏れるかもしれないので非表示にする事が推奨されている。
  • 本番環境では、エラーは STDOUT に送信するのではなく、ログに記録することが推奨されている。

  • デフォルト:On
  • 開発用:On
  • 本番用:Off

-display_errors = On
+display_errors = Off

開発用ではエラーを表示するが、本番用では表示しない設定になっている。

display_errors

https://www.php.net/manual/ja/errorfunc.configuration.php#ini.display-errors

display_startup_errors

PHPの起動シーケンス中に発生するエラーの表示は display_errors とは別に処理されるため、こちらで表示有無を設定する。

  • 構成の詳細が漏洩しないように本番環境ではオフにする事が推奨されている。

  • デフォルト:On
  • 開発用:On
  • 本番用:Off

-display_startup_errors = On
+display_startup_errors = Off

開発用ではエラーの表示が行われるのに対し、本番用では表示されない設定になっている。

display_startup_errors

https://www.php.net/manual/ja/errorfunc.configuration.php#ini.display-startup-errors

mysqlnd.collect_memory_statistics

mysqlnd によるメモリ使用統計の収集を行うかの設定。(MySQLのチューニングやモニタリングに使える)

-mysqlnd.collect_memory_statistics = On
+mysqlnd.collect_memory_statistics = Off

開発用では有効になっているのに対し、本番用では無効になっている。

mysqlnd.collect_memory_statistics

https://www.php.net/manual/ja/mysqlnd.config.php#ini.mysqlnd.collect-memory-statistics

zend.assertions

PHP が実行コードを生成する際にアサーションを含めるかの設定

設定値

  • -1:含めない
  • 0:含めるが実行時はアサーションをスキップする
  • 1:含める

  • 負の値から、または負の値への変更は、php.iniでのみ可能。
  • zend.assertions = 1 にした場合、実行時のアサーションのオン・オフの切り替えは assert.active で行える。

  • デフォルト値:1
  • 開発用:1
  • 本番用:-1

-zend.assertions = 1
+zend.assertions = -1

開発用はアサーションを含めるのに対して、本番用では含めない設定になっている。

zend.assertions

https://www.php.net/manual/ja/ini.core.php#ini.zend.assertions

assert.active

https://www.php.net/manual/ja/info.configuration.php#ini.assert.active

opcache.huge_code_pages

PHPコードを Huge Page にコピーするかどうかの設定。

  • Huge Page は Linux の機能で、メモリページングの際のチャンクサイズを増やしてパフォーマンスを向上させる機能。
  • Linux では PHP 7.0.0 以降、FreeBSD では PHP 7.4.0 以降が必要。
-;opcache.huge_code_pages=0
+;opcache.huge_code_pages=1

開発用では無効になっていて、本番用では有効になっている。

しかしどちらの環境でもコメントアウトされているので、opcache を有効化してここをチューニングする場合はコメントアウトを外してあげる必要がある。

opcache.huge_code_pages

https://www.php.net/manual/ja/opcache.configuration.php#ini.opcache.huge_code_pages

まとめ

開発時にはデバッグがしやすいように、そして本番では不要な情報の露出や無駄なリソースの消費がないように、それぞれの ini ファイルで設定されていましたね。

あなたが作っているサービスの本番環境の php.ini は、本当に本番用を元に作成されていますか?

見直してみても良いかもしれません。

ちなみに、php.ini を php.ini-development もしくは php.ini-production のどちらかから作成した場合は About this file セクションを見ると以下のように表記されているので、それをみればどちらから生成したのかがわかると思います。

# php.ini-development から作成
; This is the php.ini-development INI file.

# php.ini-production から作成
; This is the php.ini-production INI file.

GitHub ActionsにCronがあると聞いたんだ

自己紹介

agent bank開発チームのhironekoです。 個人ブログは、こちらです。

腹筋ローラーを初めて以降、筋肉痛なのか、姿勢の悪さからのコリなのかわからない苦しみが起きています。

あとウォーキングデットやゴールデンカムイを見ていついかなる状況になっても生存しなくてはいけないと感じてキャンプに興味を持ちました。

さて今回の目的

GitHub ActionsのCronで何すっかって話なのですが

最近に限ったことではなくて、僕は、数字の4桁以上を覚えるのがなかなか苦手なレベルの脳内メモリ所有者なのですが

来期のアニメの見なくてはいけないタイトルが10タイトルくらいあり、放送日時を把握し切れる訳がないじゃないか!って危機的状況に陥っている訳です。

仮にリアタイで見れなくてもアマプラさんで解決する部分もあったり絶命するわけではないのですが、個人的にHPが2割以下くらいになって日々のモチベーションがダダ下がりするのだろうと想定されるのです。

なのでよしGitHub Actionsの学習がてら毎朝か業後くらいの時間に通知行くようにしようぜ!

なんならPythonでやってみようぜ!

って昨日の夜中に思い立ったのでやってみたいとおもいます。

補足

我が家は、録画機能を有した何かしらの文明機器を持ち合わせていません。

前提

ゴール

最終的に以下のようにslackへ放送日に通知が行くようにします。 f:id:hironekosun:20201223154138p:plain

実装:Python

コードを先に晒します。

import json
import slackweb
from os import getenv
import datetime as dt
import calendar

today = dt.date.today()
dayName = calendar.day_name[today.weekday()]

if today.month < 10:
    month = f'0{today.month}'
else:
    month = today.month

data = json.load(open(f'./data/anime/{today.year}{month}.json', 'r'))

slack = slackweb.Slack(url=getenv('SLACK_WEBHOOK_URL'))

for v in data:
    if v['day_of_week'] == dayName:
        attachments = [
           {
               "fallback": 'アニメの放送時間のご案内',
               "pretext": '本日放送のアニメ',
               "fields": [
                   {
                       "title": 'タイトル',
                       "value": v['title'],
                   },
                   {
                       "title": '放送時間',
                       "value": v['publish_at'],
                       "short": "true"
                   },
                   {
                       "title": 'チャンネル',
                       "value": v['channel'],
                       "short": "true"
                   }
               ]
           } 
        ]
        slack.notify(attachments=attachments)

解説

  • 特別語ることは少ないです。

  • slackweb

こちらのライブラリが一番手っ取り早く簡単そうだったので使いました。

所感としては、ほんと単純なコードで通知することが可能なので誰でも迷わず実装が行えると思います。

  • getenv

getenvを使用して環境変数を取得してslackのweb hook urlをコードにハードコードしないようにします。

  • 全体

読めばわかるとは思うのですが、予めJSONのfileにデータを入れておきそのデータを元に今日放送をするアニメかどうかを判定して通知を行っています。

.
├── attachments.json
├── data
│   └── anime
│      ├── 202001.json
│      └── 202012.json
├── read.py
└── requirements.txt
  • JSON 下記の形式で通知対象にしたい情報を入れておきます(ここはスクレイピングでどうにかしたい)

file名は、必ずyyyymm.jsonとします。

[
    {
        "title": "転生したらスライムだった件",
        "publish_at": "23:00",
        "channel": "TOKYO MX",
        "day_of_week": "Tuesday"
    },
    ....
]

実装:GitHub Actions

  • こちらも先にコードを晒します。
on: 
  schedule:
    - cron: '55 0 * * *'

jobs:
  slack:
    name: Run slack
    runs-on: ubuntu-latest
    env:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up Python 3.8
        uses: actions/setup-python@v1
        with:
          python-version: 3.8
      - name: pip install
        run: pip install -r requirements.txt
      - name: post
        run: python read.py
  • cron

注意点として5分以下は動かないっぽいのとdefault branch以外は、cronの対象にならないとのこと

またこのcronが結構いい加減なので5分くらいのズレは、デフォです。

なので今回実行時間を10時に実行されるといいなーって気持ちでUTC 00:55 = 日本時刻 09:55 に設定しました。

GitHub Actionsよろしく!

  • secrets

今回slackのweb hook urlを使いますのでコードにハードコードしたくなく、外に晒すのもなーっていう気持ちなのでGitHubの機能を使用しました

Settings > Secretsで設定ページへ遷移します。New repository secretボタンを押下し追加します。

使い方は${{ secrets.KEY_NAME }}となります。

今後

漫画やアニメが好きなんですが既存の世に出ているサービスでは、僕のやってほしいことを満たすサービスが1mmもないのでこれを気に作ってやるんだから!って気持ちがふつふつと湧いてきました。

年末年始、時間があるようでないようなものですが積読している書籍読みつつ、開発していきたいなって気持ちです。

最後に独り言

グーカレでよくね?という意見は、受け付けますん。すみませんブログネタ考えたあとに振り返ったらグーカレの定期でよくねって考えに至りました。

"OK Google" + Nature Remo で部屋の照明をコントロールする

ROXX エンジニアの匠平 (@show60)です

新型コロナの感染防止対策のため、弊社エンジニアメンバーも多分に漏れず自宅作業がメインとなっています。

せっかく長く過ごす自宅環境なのでもっと楽しくしていこうと思い、Nature Remo の API で遊んでみました。

経緯

もう2年前になりますが、ROXX 社内の LT 大会で1位をいただき、賞品にスマートリモコン Nature Remo をいただきました。

家電をアプリでコントロールできるという代物なんですが、当時持っていた家電が古すぎてほとんど使えていませんでした。家時間が増えたことも相まって、今年になってエアコンと照明を新調したため対応家電が増えて遊べる環境が整ってまいりました。

今回はスマホで「OK Google, 休憩入ります」と話すと部屋の照明を消してくれる遊びをやってみます。

環境

  • Android スマートフォン (Pixel3)
  • Nature Remo (第二世代)
  • 照明
  • IFTTT
  • AWS API Gateway

Nature Remo の設定、照明との連携は先に済ませておきます。

想定する動作

Google Assistant での音声リクエストから消灯までの流れは以下のようになります。

  1. Google Assistant で Webhook を叩く。
  2. Webhook が AWS API Gateway にリクエスト。
  3. AWS API Gateway が Nature Remo の Web API を叩く。
  4. Nature Remo が作動して照明を操作する。

準備

Nature Remo

Nature Remo の利用を開始する際にアカウントを作成します。以下の URL から同じアカウントでログインし、アクセストークンを発行します。

アクセストークンは再表示できないので忘れずにメモっておきます。

https://home.nature.global/

Nature Remo の OpenAPI 情報はこちらにまとまっています。

https://developer.nature.global/

家電の ID を取得してみます。

エンドポイントは https://api.nature.global/1/appliances で、先ほどのアクセストークンを付けてリクエストします。

f:id:show-hei:20201223030429p:plain

登録されている家電の情報を取得してきました。アイリスオーヤマの照明であることが分かりますね。

ここで表示される家電の ID をメモっておきます。また、家電の操作の情報も記述されているので確認しておきます。

[
    {
        "id": "{家電 ID}",
        "device": {},
        "model": {},
        "light": { // ① 照明として登録されている場合、このキー名になっている
            "buttons": [
                {
                    "name": "on", // ②
                    "image": "ico_on",
                    "label": "Light_on"
                },
                {
                    "name": "off", // ②
                    "image": "ico_off",
                    "label": "Light_off"
                },
                {
                    "name": "on-100", // ②
                    "image": "ico_light_all",
                    "label": "Light_all"
                },,,,
            ]
        }
    }
]

照明を操作するエンドポイント URL は https://api.nature.global/1/appliances/{家電 ID}/①?button=② のような構成になります。

今回の場合だと https://api.nature.global/1/appliances/{家電 ID}/light?button=off となります。

接続している家電の情報を取得するだけでなく、それらの家電を操作する API も存在しており、今回は照明を消灯する操作の API を使用することにします。

API Gateway

AWS API Gateway から [API を作成] > REST API [構築] を選択します。任意の API 名を入力し作成します。

※ 今回の API は IFTTT で使うのみで他者には共有しないため、プライベート (VPC からのみアクセス可) にはしていません。

照明の操作は POST リクエストのため、[リソース] > [アクション] から POST メソッドを選択します。

メソッドの設定は以下のように行います。

  • 統合タイプ: HTTP
  • HTTP メソッド: POST
  • エンドポイント URL: https://api.nature.global/1/appliances/{家電 ID}/light?button=off
  • HTTP ヘッダー: Authorization : Bearer {アクセストークン}

設定したら再度 [リソース] > [アクション] を開き、[API のデプロイ] を選択します。デプロイするステージなど適宜入力すると API の完成です。

IFTTT

API Gateway で作った URL を IFTTT の Webhook に叩かせましょう。

IFTTT の create から新規の Applet を作っていきます。

ifttt.com

以下が Applet の作成手順になりますが、シンプルな UI であまり迷わずに直感的に作ることができると思います。

Applet 作成: "If This" 項目

  1. If ThisGoogle Assistant を選択。
  2. trigger として Say a simple phrase を選択。
  3. trigger 設定: What do you want to say?休憩入ります と入力。(句読点は入れないよう注意)
  4. trigger 設定: What do you want the Assistant to say in response?ゆっくり休んでね と入力 (ここは空欄でも可)
  5. trigger 設定: Language で Japanese を選択し、Create trigger をクリック。

Applet 作成: "Then That" 項目

  1. Then ThatWebhooks を選択。
  2. action として Make a web request を選択。
  3. web request 設定: URL に API Gateway で作成した API を入力。
  4. web request 設定: Method -> POST, Content Type -> application/json を選択し、Create action をクリック。

完成

以上の工程で完成となります。

手持ちのスマホや Google home などで「OK Google, 休憩入ります」と話せば「ゆっくり休んでね」と応えてくれて消灯までしてくれるようになりました。

さいごに

Nature Remo を声で動かすだけであればシンプルに IFTTT だけで実装できますが、そこから伝搬して他の処理につなげていけるのは自分で実装する楽しさでもあります。

ここから拡張して休憩時間の終わりになると、アラーム代わりに照明を点けてくれて、ついでに Spotify API 経由で音楽を流してくれる、みたいにすると午後も楽しくお仕事できそうです。

Laravelの環境構築が一瞬で終わった

この記事は個人ブログの転記です。

Laravelの環境構築をしようとしたら、一瞬で終わって感動したという感想メモです。
公式サイトに書いてあること以上のことは出ません。

環境構築手順

今までは、Laravelの環境構築のために、

  1. PHPを入れる
  2. Laravelコマンドを入れる
  3. Laravelをインストールする
  4. データベースとかも準備する
  5. 必要であればnodeやらyarnやらも準備する
  6. ローカルが汚れないように仮想環境を準備する

みたいな面倒なやつが必要でした。
そして、ちょいわけあって新規の環境を用意する必要があり、「面倒だなー」と思いながら環境構築をしたのですが、一瞬で終わってびっくりしました。

今やLaravelの環境構築は3ステップで終わります。

  1. Docker for Mac を入れる
  2. curl -s https://laravel.build/example-app | bash
  3. ./vendor/bin/sail up

.env.example のコピーや artisan key:generate は必要ありません。
composer install も不要です。
PHPを入れる必要すらありません。

この sail がLaravel8から入った新機能のようでした。

できる環境

Laravelの開発環境を作るライブラリのようです。

デフォルトでは

  • PHP8.0
  • MySQL8.0
  • Redis
  • MailHog
  • node ( npm )

の環境が出来上がります。
デフォルトではコメントアウトされていますが、Laravel Dusk のために selenium のコンテナも建てられます。
ちなみにPHPバージョンは7.4でも作れるようです。

dockerがわかる人であれば、 docker-compose.yml を読めば「ああなるほどね」となると思います。

操作体系

docker環境となると、「dockerわからん人どうするんじゃい!」という話が上がりそうですが、そこらへんもわかりやすくまとまってました。
./vendor/bin/sail がエントリポイントとなり、色々とできるようです。

やりたいこと 今までのコマンド sailでのコマンド
環境立ち上げ docker-compose up / php artisan serve sail up
環境破棄 docker-compose down / ( serveを終了する ) sail down
PHPコマンド php info.php sail php info.php
Composer操作 composer update sail composer update
npm操作 npm i sail npm i
Artisanコマンド php artisan make:model User sail artisan make:model User
Laravel Dusk php artisan dusk sail dusk
Laravel Tinker php artisan tinker sail tinker
Shellコマンド docker-compose exec sh sail shell

だいたいのことはできそうです。 ./vendor/bin/sail が起点になるので、エイリアスシンボリックリンクを用意すると良いと思います。

まとめ

個人開発でサクッと環境構築するにはとても便利そうです。
プロダクションで使用する場合は、もうちょっとコンテナの中を覗いたりする必要がありそうです。

PHP 8.0 の新機能を試してみよう

この記事は個人ブログと同じ内容です

www.ritolab.com


2020 年 11 月 26 日に PHP 8.0 が正式リリースとなりました。

今回は PHP 8.0 の新しい機能を使ってみようと思います。

実行環境

Docker でコンテナを作成して PHP 8.0 を動作させます。

今回は適当な場所に php80 というディレクトリを作成してそこで行っています。

php80/
└─ src/
    └─ index.php

以下のコマンドでコンテナを起動

docker run \
    -v /path/to/php80/src:/var/www/html \
    -p 8080:80 \
    -d --name test-php80-container php:8.0-apache

起動したコンテナの PHP バージョン

$ php -v
PHP 8.0.0 (cli) (built: Dec  1 2020 03:24:11) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies

Named Arguments(名前付き引数)

関数のデフォルト値の一部を変更したい時とかに、この名前付きの引数を使用する事で引き数の指定が簡略化されます。

https://wiki.php.net/rfc/named_params

例えば以下のような関数があったとして

<?php

function sample(int $value = 1, int $value_int = 1, string $valueString = 'hoge')
{
    $number = $value + $value_int;

    return "{$valueString}: {$number}";
}

sample();
// => hoge: 2

3つの引き数にはデフォルト値が指定されているので、この関数を使用時に引き数を渡さなければそれぞれデフォルト値が代入されて、文字列 hoge: 2 が返ります。

この時に例えば「最後の引き数だけを指定したい」といった場合には 3 つ全ての引き数を指定してあげなければなりませんでした。

<?php

// 第三引数だけを変更したいけど第一引数と第二引数も渡してあげる必要がある
sample(1, 1, 'foo');
// => foo: 2

これを名前付き引き数を使用することで、第三引数のみを指定する事ができるようになりました。

<?php

// 第三引数の変数名を指定して引数として渡す
sample(valueString:'abc');
// => abc: 2

「最後の引数」というのは例なので、例えばこの関数の第二引き数のみを指定する事も可能です。

<?php

// 第二引数の変数名を指定して引数として渡す
sample(value_int: 10);
// => hoge: 11

複数指定することもできます。

<?php

// 複数指定する
sample(value_int: 5, valueString:'xyz');
// => xyz: 6

関数で定義されている引数の順番でなくてもいけます。

<?php

// 指定順がバラバラでもいける
sample(valueString:'AAA', value_int: 3, value: 4);
// => AAA: 7

Constructor property promotion(コンストラクタのプロパティ昇格)

クラスをインスタンス化する際にコンストラクタでセットするプロパティへの代入と定義が簡略化できるようになりました。

https://wiki.php.net/rfc/constructor_promotion

例えば、Member クラスをインスタンス化する際に id と name を渡すとすると、これまでは以下の記述で行っていました。

<?php

class Member
{
    public int $id;

    public string $name;

    public function __construct(int $id, string $name)
    {
        $this->id   = $id;
        $this->name = $name;
    }
}
  1. プロパティ(メンバ変数)を宣言する
  2. コンストラクタでそれぞれ id と name をプロパティへ代入する

これが PHP 8 からは以下のように簡略化して記述することができるようになりました。

<?php

class Member
{
    public function __construct(
        public int $id,
        public string $name,
    ) { }
}

コンストラクタの引数のところでプロパティを宣言しているようなイメージですね。

これで「プロパティの宣言」「コンストラクタで受け取る引数の型・順番の指定」「受け取った値をプロパティへ代入する」をまとめて行っている感じ。なんかすごいな。

Union types(Union型)

型宣言を複数記述することができるようになりました。

https://wiki.php.net/rfc/union_types_v2

関数の引数・戻り値、クラスのプロパティに型宣言が行えますが、これまでは型が1つに確定している場合のみ(もしくは ? で |nullを表現)記述できる状態でした。PHP 8 からは、複数の型を記述できるようになりました。

<?php

class Sample
{
    public int|float $a;

    public function set(int|float $a)
    {
        $this->a = $a;
    }

    public function get(): int|float|null
    {
        return $this->a;
    }
}

Match expression(match式)

match 式が追加されました。

switch 式と似ているけれど、より洗練された感じになっています。

<?php

$number = 1;


/* switch 式 */
switch ($number) {
    case 1:  $result = 'one';   break;
    case 2:  $result = 'two';   break;
    case 3:  $result = 'three'; break;
    default: $result = 'other';
}

echo $result;
// => one


/* match 式 */
$result = match ($number) {
    1       => 'one',
    2       => 'two',
    3       => 'three',
    default => 'other',
};

echo $result;
// => one

match が switch と違う点

  • 値を返す
  • 比較が厳密( == ではなく === )に行われる
  • フォールスルーしないので break を書かなくて良い

シンプルな分、間違いが起こりにくい印象を受けました。 比較部分を軽視してて予期せぬ挙動したりとか、単純に break 付け忘れて意図していない挙動するとか、そういうのは無くせそうですね。

Nullsafe operator(Null 安全演算子

JavaScript 等ではお馴染みな Null 安全演算子PHP でも使えるようになりました。

<?php

$country = $session?->user?->getAddress()?->country;

「?」をアローの手前に挟むやつです。

<?php

class User
{
    private int $id;

    private string $name;

    public function __construct(int $id, string $name)
    {
        $this->id   = $id;
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

class Member
{
    public User|null $user = null;
}

$member = new Member();


$member->user->getName();
// => Fatal error: Uncaught Error: Call to a member function getName() on null

$member->user?->getName();
// => null

上記の例では、Member クラスの $userに User クラスがセットされていない場合、getName() を参照できないので Fatal error が発生しますが、 Null 安全演算子で記述することで、$member->user が null の場合は getName() を参照せずに null を返してくれるようになります。

str_contains()

対象文字列に、指定した文字列が含まれているかどうかを調べる str_contains() 関数が追加されました。

https://www.php.net/manual/ja/function.str-contains.php

<?php

$target = 'I love Grape,Apple,Orange,Strawberry and Cherry.';

$search = 'Orange';

str_contains($target, $search);
// => true

これまで strpos とか preg_match を使っていたことを考えたらシンプルになって良いかなと思います。

str_starts_with() / str_ends_with()

以下2つの関数も追加になっています。

<?php

$target = 'I love Grape,Apple,Orange,Strawberry and Cherry.';


$start = 'I';

str_starts_with($target, $start);
// => true

$end = 'Cherry.';

str_ends_with($target, $end);
// => true

他にもいろいろ

ここで取り上げたもの以外でも様々な変更点があるので PHP マニュアルを参照してみてください。

PHP: PHP 7.4.x から PHP 8.0.x への移行 - Manual

日々の「ふりかえり」のために、タスクでやることやったことの履歴を残す

agent bank 開発チームのさかもとです。

最近チームで取り組み始めたアクションで、とても効果を感じれたことがあったので書きます。

ふりかえり大事

仕事をしていく上で振り返りを行うことは、とても重要なことです。

なぜ「ふりかえり」を行うのか。
個人やチームの成果が出るまでの過程をふりかえってみることで、良くなかった点、改善できる点の改善を行い、良かった点はそれを続けられるように強化をしていきます。

ふりかえりは、チームや個人の反省会ではなく、次を良くするために行われるものです。  

はなしたいこと

slackのワークフローを使って、タスク着手時に「やること宣言」と「やったこと履歴」残すのが、ふりかえりをする際やチームでタスクを進めていく上でとても効果を感じれた。

きっかけ

チームの現状説明(タスクの進め方や、振り返りのタイミング)

agent bank開発チームでは現在こんな感じで開発を進めています

  • フルリモート
  • スクラム開発(スプリントの単位は1週間)
  • discordのボイスチャットつなぎっぱで開発
  • ATLASSIANのJiraバックログ管理
  • 一日の朝・昼・夕のタイミングでチーム内の現状把握の場を設定している

リモートワークを円滑にすすめるための実践とツール - ROXX(旧SCOUTER)開発者ブログ こっち見るともう少し詳しくどんな感じで進めてるかわかるかと思います。

ふりかえりであがったproblem

一日に最大3回チームの現状把握の場を設定して、「計画通りに進めているか?」「想定外の事態が発生していないか?」などのチェックをして、計画の修正やアサインの調整などを行っているんですが、先日のスプリントの振り返りでこのようなproblemが上がりました。

f:id:skmtko:20201111142339p:plain
進捗の遅れから想定外のタスク、隠れた仕様・懸念点などを洗い出したい

背景としては、タスクに対してのポイントの付け方を以下のように、ストーリーにポイントが付いていればポイント付けを行い、タスクにポイントがつくのはストーリーに紐付かない場合のときだけにしているのが、関係してそうでした。

f:id:skmtko:20201111144255p:plain
タスクへのポイントの付け方

※リファインメントの際に、ストーリーにポイント付を行い、プランニングの際にポイントが紐付かないタスクにポイント付を行っています

結果として1日の中で現状把握の場を設けてはいるが、実際にタスクにかけるつもりだった時間と、実際に掛かった時間が見えずらい状態が続いていました。

深く記憶に残るような、よほどの大事故でもない限り、一日のふりかえりで振り返ることは難しいですね。「きのうの晩ごはん」を思い出すのも難しくなってしまった我々にとっては、タスクの中でいくつも発生する些細な出来事や、タスクの開始時間、経過時間を記憶だけですべて鮮明に振り返るのはおそらく不可能です。

「想定よりもタスクが大きかったが検知できるようになる」をゴールとしたアクションを出そうといういことで、今回の「slackにやること宣言する」をすることになりました。

やったこと

やったことはシンプルでこんな感じのことをやっていました

  1. チームのタスク進捗用のslackチャネルを作成した

  2. チームメンバーはタスク着手時に、slackワークフローを用いて、やること、着手開始時間、完了目標時間などを投稿

  3. タスクを進めながら、必要に応じて都度進捗をスレッドに投稿する

  4. タスクが完了したら、完了したことを書く

  5. 区切りの時間(昼休み後の昼会や夕会)の振り返りに、活用する

実際にはこんな感じ

ワークフローの入力

f:id:skmtko:20201111150053p:plain
ワークフロー入力のイメージ

終了時刻は任意にしているが、開始から終了までに2時間以上かかっていた場合には、ふりかえりの際に、なぜ時間超過してしまったのかを話すトリガーにしている。

ワークフロー投稿の活用イメージ

f:id:skmtko:20201111152909p:plain
こんな感じでスレッド作ってふりかえれるようにする

終了したことと、終了した時刻だけは必ずわかるようにして、作業中のスレッドは各自自由に投稿するようにしている

今のところ、進捗の投稿を行うかどうかは各自に任せて自由に投稿している。
 例)「〇〇完了した」「✗✗が判明したのでタスクを追加起票した」「△△が複雑で延長した」

他のタスクを実施しているメンバーが、共有や依頼を投げたりにも使ってみている
 例)「Aのタスクで演る予定だったけど、こちらで〇〇の実装もお願い!」

うれしかったこと

みんなが嬉しかったこと

実際に2週間運用してみたところ、メンバーの全員が好感触でした。こんなかんじ。

  • 忘れがちになタスク着手時の時系列を残せてふりかえれるのがよい
  • デザインタスクをひとりでやることが多いが、共有できて安心する(開発チームにデザイナーも一人います)

ふりかえりの材料が目に見える状態で残っているというのが、とても大きかったみたいです。
何もわからないことは、怖いですもんね。
slackのワークフローで最初に投稿するようにしたのが、着手時の投稿ハードルを下げたんですかね。着手宣言のアクションを初めて1日後にワークフローをすぐ作ってくれたメンバーに感謝です。

他の人はしらんけど俺はこれがうれしかったこと

ふりかえりの際の材料になることはもちろんとても良かったんですが、その他にも個人的に、以下のような嬉しさがありました。

  • 他のメンバーが何をやっているかがすぐ分かる
  • 着手時に何をすべきなのかが整理できる
他のメンバーが何をやっているかがすぐ分かる

ボイスチャットつなぎっぱといえども、皆がずっと話しっぱで、逐一何しているか話しているわけではありません。(たまにずっと喋りながらタスクを進めるメンバーがいたり、全員でモブですすめるみたいなケースはありますが...)

おおよそ誰がどのタスクを着手するかをわかるようにしていますが、計画通りに行くとは限りません。
誰が何をやっているのかすぐにわからない状態では、次何をするかを迷ってしまいがちでした(わたしが)

着手宣言と経過を見えるようにすることで、その日のたすくをより効率的にすすめるための判断がやりやすくなりました。
(どんどんタスクをすすめるべきか、ほかメンバーのサポートに入るべきか、計画を修正するべきかなどの判断がやりやすくなったとおもう)

 着手時に何をすべきなのかが整理できる

着手時に完了目標時間を入れることによって、タスク開始時に再度タスクのプランニングを行うことになります。
可能ならば30分とか10分単位で完了できることを整理し直すことで、タスクの中で明確に何をやるべきなのかが見えてくると思います。
タスクが大きすぎる場合は、ワークフローで投稿する際に宣言しなくとも、スレッド内に「次の30分で〇〇を終わらせる」などと宣言してタイムボックスを設定すれば、細かくやることを整理も可能でさらには、ふりかえりの際に、より詳細な出来事として振り返ることが可能です。

あと、タスクに紐づく共有をできたのも良かった

個人的には、15分スプリント*1にトライしてみたい感あったんですが(やれるイメージが、まだあまり浮かばないけど)、とりあえずはslackの「やること宣言」だけでも今の顕在化した課題は解決できそう。

さいごに

「slackにタスクのやることやったことの履歴を残す」といったシンプルなアクションだったんですが、現在のチームとしてはすごく効果を感じることができました。このアクションが、これからのふりかえり、改善のために役立って行くことと思います。

また続けていくうちに、違った方法になるかもしれませんが、もしそうなるとしたらチームがより良くなるための動きの一つだと思います。

今回のアクション自体も、実行の1日目から「slack投稿先の変更」「ただのslack投稿→ワークフローで投稿」「ワークフローの入力項目変更」などと、ふりかえりを重ねることに、今のチームがより良くできるように変化を重ねていました。

この調子で、さらにプロダクトをより良くできるチームになれるように、開発を進めていきたいところです。

おまけ

弊社では開発チームのメンバーをとても募集しています。 ちょっとでも興味があって、話くらいなら聞いてやるか!という方は私のtwitterにでも連絡くれると、とてもありがたいです。

こちら さかもとこうへい (@12kohe3i) | Twitter

Next.jsでStrict CSPを実現する

この記事は個人ブログと同じ内容です。

kotamat.com

業務では Nuxt.js を使用しているのですが、CSP の nonce の対応ができておらず、Next.js だとどうなんだろうと触ってみたらメチャクチャ簡単に設定できる事がわかったので、その方法について紹介します。

やりたいこと

CSP の対応のなかで Google が提唱しているやり方としてStrict CSPというのがあります。 これは簡単に言うと下記のように CSP の設定を行えば モダンなブラウザにおいて プロダクションレベルのセキュリティーが担保できるよというものです。

Content-Security-Policy:
  object-src 'none';
  script-src 'nonce-{random}' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;
  base-uri 'none';

このなかで大事なものとしては strict-dynamic が挙げられます。これは、CSP Level3 から導入されたディレクティブで、ほかのドメインホワイトリストは無視した上で、'nonce-{random}'で指定された <script> タグのスクリプトを許可し、かつその先で生成された別のスクリプトに関しても同様に許可するというものです。

この挙動により、開発者はトップレベルのスクリプトに関して nonce をつければ良くなり、攻撃者はその nonce を奪えない限り XSS ができなくなるため、ホワイトリスト形式での設定より安全になるということで、非常に便利に対策ができるようになります。

注意点としては、モダンなブラウザでなければ strict-dynamic は解釈してくれないので、そういったブラウザでは脆弱性は防ぎきれないというところになります。 また Safari はまだ対応していません。(モダンなブラウザではないということですかね…?)

https://caniuse.com/?search=strict-dynamic

対応方法

/pages/_document.tsx にて、nonce の生成と、Head,NextScript への反映を行います。

import { randomBytes } from "crypto";
import Document, { Html, Head, Main, NextScript } from "next/document";

type WithNonceProp = {
  nonce: string;
};
export default class MyDocument extends Document<WithNonceProp> {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    const nonce = randomBytes(128).toString("base64");
    return {
      ...initialProps,
      nonce,
    };
  }
  render() {
    const nonce = this.props.nonce;
    const csp = `object-src 'none'; base-uri 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: http: 'nonce-${nonce}' 'strict-dynamic'`;

    return (
      <Html>
        <Head nonce={nonce}>
          <meta httpEquiv="Content-Security-Policy" content={csp} />
        </Head>
        <body>
          <Main />
          <NextScript nonce={nonce} />
        </body>
      </Html>
    );
  }
}

これだけです。

Next.js では、SSR では当然のこと、SSG する際にもこの処理を適応してくれるため、例えば下記のようなページコンポネントがあったとしても

import { GetStaticProps } from "next";
import { FC } from "react";

type Props = {
  name: string;
};
const SSG: FC<Props> = ({ name }) => {
  return <div>{name}</div>;
};

export default SSG;

export const getStaticProps: GetStaticProps<Props> = async (ctx) => {
  return { props: { name: "hoge" } };
};

next export 後のファイルに nonce がちゃんと反映されています。 ただ、当然ですが、SSG だと nonce の本来の持つべき役割である 「リクエストごとに生成し直す」ことができないため、SSG を前提としたプロダクトでは攻撃者の攻撃にさらされる危険性があります。

まとめ

Next.js にて nonce を用いた StrictCSP を実装する方法を紹介させていただきました。 モダンではないブラウザが対応してくれれば、相当対策をしやすくなるので、ブラウザの対応に期待したいですね。

余談

Nuxt.js では、この辺で HTML タグが直書きされていることによって nonce を入れる余地がないため、今回のやりたいことはできない感じになっています。 その代わり inlineScript の hash 値を CSP に埋め込む形で実装されているため、アプリケーションのソースコードに閉じる限りはそれなりに硬い対策になるようになっているようです。