20201201のPHPに関する記事は22件です。

異業種から転職して感じたwebエンジニアに必要な素養

とりあえず箇条書き

・分かりづらいことを分かりやすく説明する能力
・急な内容修正などに対する対応力と柔軟性
・チームの人に対する思いやりと気遣い(コードの工夫やコミュニケーションなどで)
・自分の書くコードに過度な愛着やこだわりを持たない
・自分の思い通りにいかなくても許容する広い心
・ファイル内容やDB構造など広く記憶する記憶力
・どんなバグにもへこたれない強いメンタル
・行き詰ったらすぐに質問をする
・手遅れになる前に相談する
・小まめに進捗報告をする(実は気付いていない内容がよくある)
・どんな状況においてもポジティブシンキング
・自分の間違いを認められること
・相手の間違いを許せること
・意見が合わなくてもレスバしないでうまく妥協点を見つける、その場を丸く収める
・納期を守る、約束を守る
・ダブルチェック、確認をしっかりとする
・あいさつをする
・遅刻しない
・整理整頓
・清潔感
・笑顔

もうエンジニア関係なくなっちゃった!

今日の名言

If today were the last day of my life, would I want to do what I am about to do today?
(もし今日が人生最後の日だとしたら、今しようとしていることが、本当にしたいことだろうか?)

-スティーブ・ジョブズ

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

dockerを使用してさくっとPHP8を触ってみた

dockerを使用してさくっとPHP8を触ってみた

PARONYM Advent Calendar 2020 - Qiita の2日目です。

はじめに

2020/11/26にPHP8がリリースされました。
ということで、自身のローカル環境をなるべく汚さずにPHP8の新機能を触ってみることにしました。

前提条件

  • dockerがインストールされていること

きっかけ

PHP8の新機能を触ってみたい!でも自身のPCを汚したくない!
PHP8を試す環境として、当然ながらプロダクトの開発環境で試すわけにはいきません。(本番環境なんて以ての外)
となるとローカルPCで試すのが妥当ですが、インストールや設定の手間、もしもの時のバージョンの切り替えを考えると一気に面倒になります。
今回はPHP8の機能を触るのが目的であり速度性能を試すわけではないため、さくっとdockerを使用してPHP8を実行することにしました。

コード

今回試したコードは全てGithubにコミットしています。
https://github.com/y-kakinuma-paronym/php8-docker

dockerでPHP8の実行

$ docker run --rm -v `pwd`:/app -w /app php:8.0-rc php {実行したいファイル}
  • PHPの実行後、コンテナは削除する : --rm
  • カレントディレクトリをコンテナの/appにマウント : -v `pwd`:/app
  • コンテナ内の作業用ディレクトリは/app : -w /app
  • dockerイメージは php:8.0-rc を使用
  • php実行 : php {実行したいファイル}

毎回コマンドを打つのは面倒なのでシェルスクリプトを作成しておくと楽です。

exec_php.php
#!/bin/sh
docker run --rm -v `pwd`:/app -w /app php:8.0-rc php $1

# ./exec_php.sh match_expression.php   

PHP8新機能

PHP8の新機能はここから抜粋しています。
PHP: PHP 8.0.0 Release Announcement

Named arguments

引数を渡すときに名前を指定できるようになりました。
https://wiki.php.net/rfc/named_params

<?php
// Named arguments
// https://wiki.php.net/rfc/named_params

$str = "<a href='test'>Test</a>";

// PHP7
// $result = htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);

// PHP8
// double_encodeの引数のみをデフォルト引数から変更したい場合、引数の名前を指定する
$result = htmlspecialchars($str, double_encode: false);
echo $result . "\n"; // &lt;a href='test'&gt;Test&lt;/a&gt;

?>

Constructor property promotion

コンストラクタでのプロパティ初期化が簡略化できるようになりました。
https://wiki.php.net/rfc/constructor_promotion

<?php
// Constructor property promotion
// https://wiki.php.net/rfc/constructor_promotion

class Point {
    // PHP8
    public function __construct(
        public int $x = 0,
        public int $y = 1,
        public int $z = 2,
    ) {
        print "Point constructor\n";
    }

    // PHP7
    // public int $x;
    // public int $y;
    // public int $z;
    // public function __construct(
    //     int $x = 1,
    //     int $y = 2,
    //     int $z = 3
    // ) {
    //     $this->x = $x;
    //     $this->y = $y;
    //     $this->z = $z;
    // }

    public function showClassParameter() {
        print_r([$this->x, $this->y, $this->z]);
    }
}

$obj = new Point();
$obj->showClassParameter();

?>

Union types

共用体型(union type)が追加されました。
https://wiki.php.net/rfc/union_types_v2

<?php
declare(strict_types=1);

// int と string を型指定
$f = function (int|string $v) {
    var_dump($v);
};

$f(100); // ok
$f("abc"); // ok
$f(true); // ng

Match expression

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

<?php
// Match expression
// https://wiki.php.net/rfc/match_expression_v2

switch (1) {
    case 0:
        $result = "Foo\n";
        break;
    case 1:
        $result = "Bar\n";
        break;
    case 2:
        $result = "Baz\n";
        break;
}
echo $result; // Bar

/**
 * switchとの違い
 * ・一致した結果を変数に代入することが可能
 * ・一致は一つのみをサポート。breakなどは必要なし
 * ・一致は厳密な比較
 */

echo match(1) {
    0 => "Foo\n",
    1 => "Bar\n",
    2 => "Baz\n"
};
// Bar

echo match (8.0) {
    '8.0' => "Oh no!",
    8.0 => "This is what I expected",
};
// This is what I expected
// ↑厳密な比較のため

?>

switchとの違いについての説明はコード内に記載しています。特に注意が必要なのが、一致は厳密な比較であるところです。

Nullsafe operator

null演算子が追加されました。
https://wiki.php.net/rfc/nullsafe_operator

<?php
// Nullsafe operator
// https://wiki.php.net/rfc/nullsafe_operator

class Session
{
    public $user = null;
}

$session = new Session();

/**
 * nullsafe演算子「?」を用いることでチェーン内の1つの要素で評価が失敗すると、
 * チェーン全体の実行が中断され、nullを返す
 */
$country = $session->user?->getAddress()?->country;
var_dump($country) // null

?>

$session->user?->getAddress()?->country;
例では↑のuserがnullのため失敗となり、nullsafe演算子はnullを返します。

PHP8の所感

PHP8の目玉はなんと言ってもJITコンパイラーによる処理の高速化ですが、こういった細かい機能も実装が楽になるため嬉しい追加でした。
特にMatch expressionは、JSONを作成するためにarrayを組み立てるときに三項演算子より複雑な分岐ができるため非常に便利だと思います。

$result = 1;
$json = [
    "resutl" => match($result) {
        0 => "Failed",
        1 => "Success",
        2 => "No Updated"
    },
];

echo json_encode($json); // {"resutl":"Success"}

Nullsafe operatorもうまく利用できれば、if文が減りコードの可読性を向上させることができそうです。

まとめ

今回はPHP8の新規機能をDockerを使用して自身のPCをなるべく汚さずに試してみました。
今回紹介していないPHP8の新機能はまだまだありますので、公式サイトを参照してください。
PHP: PHP 8.0.0 Release Announcement

Dockerを利用すれば、PHP8に限らず様々な環境を楽に構築できるのでこれからもどんどん利用していきたいと思います。

参考

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

laravel 6.x migrations-generatorがインストールできない問題対応

既存DBのmigrationsファイルを作成したい

migrations-generatorをインストールする

composer require --dev "xethron/migrations-generator"

larabel 6.xでは下記のようなエラーになる

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - xethron/migrations-generator v2.0.0 requires way/generators dev-feature/laravel-five-stable -> no matching package found.
    - illuminate/support v5.7.9 requires nesbot/carbon ^1.26.3 -> satisfiable by nesbot/carbon[1.26.3, 1.26.4, 1.26.5, 1.26.6, 1.27.0, 1.28.0, 1.29.0, 1.29.1, 1.29.2, 1.30.0, 1.31.0, 1.31.1, 1.32.0, 1.33.0, 1.34.0, 1.34.1, 1.34.2, 1.34.3, 1.34.4, 1.35.0, 1.35.1, 1.36.0, 1.36.1, 1.36.2, 1.37.0, 1.37.1, 1.38.0, 1.38.1, 1.38.2, 1.38.3, 1.38.4, 1.39.0, 1.39.1] but these conflict with your requirements or minimum-stability.

    (省略)

    - don't install illuminate/support v5.0.4|don't install laravel/framework v6.18.20
    - Installation request for laravel/framework (locked at v6.18.20, required as 6.x) -> satisfiable by laravel/framework[v6.18.20].
    - Installation request for xethron/migrations-generator ^2.0 -> satisfiable by xethron/migrations-generator[v2.0.0, v2.0.1, v2.0.2].

Potential causes:
 - A typo in the package name
 - The package is not available in a stable-enough version according to your minimum-stability setting
   see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.
 - It's a private package and you forgot to add a custom repository to find it

Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.

Installation failed, reverting ./composer.json to its original content.

ググると同じ問題に直面してるひとが多数。(https://github.com/Xethron/migrations-generator/issues/191)
これや https://github.com/oscarafdev/migrations-generator

composer require oscarafdev/migrations-generator --dev

これを https://github.com/kitloong/laravel-migrations-generator

composer require --dev "kitloong/laravel-migrations-generator"

試してみろと書かれているのがこれらもうまく行かない・・・

解決方法

laravelのバージョンの互換性の問題だしlaravelのバージョン下げてしまえばいい
そりゃそうでしょって話だが今回migrationsファイル作るだけなので
別プロジェクトをlaravel5.4で立ち上げてそこで作ったものをコピーすれば良いと考えた

Version指定してlaravelをインストール

composer create-project "laravel/laravel=5.4.*" sampleproject
cd sampleproject
composer require oscarafdev/migrations-generator --dev

うまくインストールできた。
app.phpに下記を追加して

config/app.php
Way\Generators\GeneratorsServiceProvider::class,
Xethron\MigrationsGenerator\MigrationsGeneratorServiceProvider::class,

.envを修正して既存DBにつなげたあと

php artisan migrate:generate
Do you want to log these migrations in the migrations table? [Y/n] :
 > Y

 Next Batch Number is: 2. We recommend using Batch Number 0 so that it becomes the "first" migration [Default: 0] :
 > 0

でmigrationsファイル作成してくれました

ちなみに

php artisan migrate:generate table1,table2,table3,table4,table5

でテーブルの指定ができて

php artisan migrate:generate --ignore="table3,table4,table5"

で実行しないテーブルの指定ができるみたいです。

互換性の問題ってわかってるならあれこれやる前にバージョン下げてしまえばよいだけの話でした

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

Laravel6.xからLaravel8.xへアップグレード

まえがき

6.xがLTSだし6.xで開発しはじめていたが、ローンチ時期的に次のLTSバージョンが出る、もしくは近いことが想定されるのでいっそ次にバージョンを上げる際のコストを下げようの気持ちで8.xまでアップグレードするよ

アップデート前情報

PHP

PHP 7.4.3 (cli) (built: Feb 20 2020 21:53:46) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.3, Copyright (c), by Zend Technologies
    with Xdebug v2.9.6, Copyright (c) 2002-2020, by Derick Rethans

Laravel

Laravel Framework 6.18.35

composer.json

  • 実際に利用しているPJなのでアップデートとかには関係ないライブラリもあります
    "require": {
        "php": "^7.4",
        "ext-fileinfo": "*",
        "ext-json": "*",
        "ext-pdo": "*",
        "ext-redis": "*",
        "aws/aws-sdk-php-laravel": "~3.0",
        "encore/laravel-admin": "^1.8",
        "fideloper/proxy": "^4.0",
        "guzzlehttp/guzzle": "^7.0",
        "lampager/lampager": "^0.4.0",
        "lampager/lampager-laravel": "^0.4.5",
        "laravel/framework": "^6.2",
        "laravel/tinker": "^2.0",
        "lcobucci/jwt": "^3.3",
        "vinkla/hashids": "^7.0"
    },
    "require-dev": {
        "barryvdh/laravel-ide-helper": "^2.6",
        "brianium/paratest": "^4.2",
        "deployer/deployer": "^6.8",
        "deployer/recipes": "^6.2",
        "facade/ignition": "^1.4",
        "friendsofphp/php-cs-fixer": "^2.16",
        "fzaninotto/faker": "^1.9.1",
        "laravel/telescope": "^3.2",
        "mockery/mockery": "^1.0",
        "nunomaduro/collision": "^3.0",
        "phpmd/phpmd": "^2.9",
        "phpunit/phpunit": "^9.0",
        "squizlabs/php_codesniffer": "^3.5"
    }

変更していく

下記のドキュメントを参考にしながら行っていく

PHP要件

今回はもともとPHP7.4だったので特に変更無し

Laravel依存パッケージのアップデート

6 to 7

laravel/frameworkを^7.0へ
nunomaduro/collisionを^4.1へ
phpunit/phpunitを^8.5へ
laravel/tinkerを^2.0へ
facade/ignitionを^2.0へ

7 to 8

guzzlehttp/guzzleを^7.0.1へ
facade/ignitionを^2.3.6へ
laravel/frameworkを^8.0へ
laravel/uiを^3.0へ
nunomaduro/collisionを^5.0へ
phpunit/phpunitを^9.0へ

変更したcomposer.json

  • laravelの依存ライブラリ以外にも対応が必要な場合もあるのでPJごとによしなに
    "require": {
        "php": "^7.4",
        "ext-fileinfo": "*",
        "ext-json": "*",
        "ext-pdo": "*",
        "ext-redis": "*",
        "aws/aws-sdk-php-laravel": "~3.0",
        "encore/laravel-admin": "^1.8",
        "fideloper/proxy": "^4.0",
-       "guzzlehttp/guzzle": "^7.0",
+       "guzzlehttp/guzzle": "^7.0.1",
        "lampager/lampager": "^0.4.0",
        "lampager/lampager-laravel": "^0.4.5",
-       "laravel/framework": "^6.2",
+       "laravel/framework": "^8.0",
        "laravel/tinker": "^2.0",
+       "laravel/ui": "^3.0",
        "lcobucci/jwt": "^3.3",
-       "vinkla/hashids": "^7.0"
+       "vinkla/hashids": "^9.0"
    },
    "require-dev": {
        "barryvdh/laravel-ide-helper": "^2.6",
-       "brianium/paratest": "^6.0",
+       "brianium/paratest": "^6.8",
        "deployer/deployer": "^6.8",
        "deployer/recipes": "^6.2",
-       "facade/ignition": "^1.4",
+       "facade/ignition": "^2.3.6",
        "friendsofphp/php-cs-fixer": "^2.16",
        "fzaninotto/faker": "^1.9.1",
-       "laravel/telescope": "^3.2",
+       "laravel/telescope": "^4.0",
        "mockery/mockery": "^1.0",
-       "nunomaduro/collision": "^3.0",
+       "nunomaduro/collision": "^5.0",
        "phpmd/phpmd": "^2.9",
        "phpunit/phpunit": "^9.0",
        "squizlabs/php_codesniffer": "^3.5"
    }

このまま compose update すると最後のコマンド実行でエラーになるので先にSynfony5対応を行ってしまう

Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 255

Symfony5へのアップグレード

App\Exceptions\Handlerの変更

  • app/Exceptions/Handler.php を8.xの内容でコピーする
  • 見た感じ継承して親の処理呼んでただけの部分が消えた
    • 残したければ Exception を受けてるところを Throwable
  • 自前実装分があればよしなに

https://github.com/laravel/laravel/blob/8.x/app/Exceptions/Handler.php

セッションの設定を変更

  • config/session.php を8.xの内容でコピーする
  • コメント以外は下記のような違いになっている
  • 自前実装分があればよしなに
app\config\session.php
~~

- 'secure' => env('SESSION_SECURE_COOKIE', false),
+ 'secure' => env('SESSION_SECURE_COOKIE'),

~~

- 'same_site' => null,
+ 'same_site' => 'lax',

];

https://github.com/laravel/laravel/blob/8.x/config/session.php

compose updateの実行

上記の対応まで行ってからcomposer updateを行い、エラーが出なければOK
エラーが出た場合は内容見て解決するのが良いとは思います

ここまでで最低限の対応

$ composer update

気になればキャッシュも消しておく

$ artisan optimize:clear

更新後バージョン

Laravel Framework 8.16.1

Next step

PJというか環境によっては上記までの対応では不足しているので動作確認をしながら、エラーや挙動がおかしい箇所に関してアップグレードガイドを参考にしていく

特に「影響の可能性: 高い」の部分は要チェック

参考までに、「影響の可能性: 低い」とされていても 自分のPJでは $model->getOriginal を利用していたので対応が必要になったり、 app/Providers/RouteServiceProvider.php をごにょごにょしていたので変更が必要だったり

ライブラリを更新した影響でLaravelと関係ないところで更新を迫られたりとまぁちょいちょいありました

アップグレードガイド見るよりコード差分見て行くほうが早いかもしれない
https://github.com/laravel/laravel/compare/6.x...8.x

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

cordova-plugin-purchase用の消耗型課金レシート検証APIをLaravelで実装する

Cordovaで課金処理を行うためのプラグイン、cordova-plugin-purchaseには各プラットフォームのレシートを検証するためのAPI呼び出し処理を追加することができます。
https://github.com/j3k0/cordova-plugin-purchase/blob/master/doc/api.md#receipt-validation

このレシート検証APIをLaravelで作ってみます。

課金を実装したCordovaアプリ自体の作成はこちらの記事でまとめています。
Cordova(Monaca)でアプリ内課金を実装する

APIのリクエスト、レスポンス仕様

プラグイン側でリクエストレスポンスの仕様が決められています。
https://github.com/j3k0/cordova-plugin-purchase/blob/master/doc/api.md#validator

リクエスト

URL

好きなURLを設定できます。
標準以外のパラメータを渡したい場合はパスパラメータなどを利用しましょう。

メソッド

POST

リクエストボディ

{
  "additionalData" : null,
  "alias" : "monthly1",
  "currency" : "USD",
  "description" : "Monthly subscription",
  "id" : "subscription.monthly",
  "loaded" : true,
  "price" : "$12.99",
  "priceMicros" : 12990000,
  "state" : "approved",
  "title" : "The Monthly Subscription Title",
  "transaction" : { // 各ストアのレシート情報が入る },
  "type" : "paid subscription",
  "valid" : true
}

transactionの中身は各ストア事にこのようになります。
https://github.com/j3k0/cordova-plugin-purchase/blob/master/doc/api.md#transactions

iOS
"transaction" : {
    "appStoreReceipt":"appStoreReceiptString", // BASE64エンコーディングされたレシート情報
    "id" : "idString", // トランザクションID
    "original_transaction_id":"transactionIdString", // 購読型の時にセットされる
    "type": "ios-appstore" // ストアの識別
}
Android
"transaction" : {
    "developerPayload" : undefined, // オプション
    "id" : "idString", // トランザクションID
    "purchaseToken" : "purchaseTokenString",
    // レシート情報
    "receipt" : "{\"autoRenewing\":true,\"orderId\":\"orderIdString\",\"packageName\":\"com.mycompany\",\"purchaseTime\":1555217574101,\"purchaseState\":0,\"purchaseToken\":\"purchaseTokenString\"}",
    "signature" : "signatureString", // 署名
    "type": "android-playstore" // ストアの識別
}

レスポンス

返すレスポンスによって呼び出し元に戻った時に.verified()に入るか.unverified()に入るか決まります。

成功(verified)

レスポンスコード

200

レスポンスボディ

{
    "ok" : true,
    "data" : {
        "transaction" : { // リクエストボディのトランザクションをセット }
    }
}

失敗(unverified)

レスポンスコード

200または200以外でも失敗と判断される

レスポンスボディ

{
    "ok" : false,
    "data" : { // エラーコード
        "code" : 6778003
    },
    "error" : { // エラーメッセージ。好きに設定できる。
        "message" : "The subscription is expired."
    }
}

アプリ側でハンドルするためにエラーコードは以下が定義されています。

store.INVALID_PAYLOAD   = 6778001;
store.CONNECTION_FAILED = 6778002;
store.PURCHASE_EXPIRED  = 6778003;
store.PURCHASE_CONSUMED = 6778004;
store.INTERNAL_ERROR    = 6778005;
store.NEED_MORE_DATA    = 6778006;

消耗型課金の検証

消耗型(Androidなら消費型)の課金に関して、各プラットフォーム側でレシートの検証方法が用意されています。

プラットフォームごとの検証

iOS(App Store)

iOSの場合はApp Storeの用意するAPIにレシートを送信することで検証することができます。
https://developer.apple.com/documentation/appstorereceipts/verifyreceipt

環境 エンドポイント メソッド
Sandbox https://sandbox.itunes.apple.com/verifyReceipt POST
Production https://buy.itunes.apple.com/verifyReceipt POST

Sandboxアカウントで発行したレシートはSandboxのエンドポイントに送信しなければいけません。
本番のエンドポイントにSandboxのレシートを送信すると「{"status":21007}」が返ります。

レスポンスのステータスが0なら正しいレシート。
戻されたバンドルIDが正しいかなどのチェックを追加で行います。

Android(Google Play)

Androidの場合は、レシートの署名(signature)を検証することで、レシートが改ざんされていないかチェックできます。
リクエストで送られてきたreceiptsignatureをGoogle Play Consoleから取得できるRSA公開鍵で検証します。

公開鍵のBase64文字列はGoogle Play Consoleから取得します。
image.png

取得した文字列をpublic.txtに保存して下記コマンドを実行してPEM形式にします。

$ base64 -d public.txt > public.der
$ openssl rsa -inform DER -outform PEM -pubin -in public.der -out public.pem
writing RSA key
$ cat public.pem
-----BEGIN PUBLIC KEY-----
****************************************************************
****************************************************************
****************************************************************
****************************************************************
****************************************************************
****************************************************************
********
-----END PUBLIC KEY-----

これで検証が行えます。

その他の検証

その他にも以下を検証しておいた方がよさそうです。

  • バンドルID(パッケージ名)がアプリの識別子と一致すること
  • プロダクトIDが存在する商品のIDであること
  • トランザクションIDがまだ処理されていないこと
  • アプリのビジネスロジックに関するサーバサイドチェック

APIの実装例

Laravelでの実装例です。

api.php
Route::post('/verify-purchase', 'Api\PurchaseController@verifyReceipt');
PurchaseController.php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

class PurchaseController extends Controller
{
    private $bundle_id;
    private $product_ids;
    private $pubkey;
    private $error_codes = [
        "INVALID_PAYLOAD" => 6778001,
        "CONNECTION_FAILED" => 6778002,
        "PURCHASE_EXPIRED" => 6778003,
        "PURCHASE_CONSUMED" => 6778004,
        "INTERNAL_ERROR" => 6778005,
        "NEED_MORE_DATA" => 6778006,
    ];

    public function __construct()
    {
        $this->bundle_id = env('BUNDLE_ID');
        $this->product_ids = env('PRODUCT_IDS');
        $this->pubkey = env('PUBKEY');
    }

    public function verifyReceipt(Request $request)
    {
        $success_response = [
            'ok' => true,
            'data' => [
                'transaction' => null,
            ],
        ];

        $error_response = [
            'ok' => false,
            'data' => [
                'code' => $this->error_codes['INVALID_PAYLOAD'],
            ],
            'error' => [
                'message' => "invalid receipt"
            ],
        ];

        // Validation check
        $validator = Validator::make($request->all(), [
            'id'                 => "required|string|in:{$this->product_ids}",
            'transaction'        => 'required|array',
            'transaction.type'   => 'required|in:ios-appstore,android-playstore',
        ]);

        // Validation error
        if($validator->fails()) {
            $errors = $validator->errors()->all();
            Log::notice($errors);

            $error_response['error']['message'] = $errors;
            return $error_response;
        }

        $product_id = $request->input('id');
        $transaction = $request->input('transaction');

        Log::info($transaction);
        $success_response['data']['transaction'] = $transaction;

        // Verify each platform
        switch($transaction['type']){
            case 'ios-appstore':
                Log::info('Verify App Store.');
                if(!$this->verifyAppStore($transaction)){
                    $error_response['error']['message'] = 'Verify App Store Failed.';
                    return $error_response;
                }
                break;

            case 'android-playstore':
                Log::info('Verify Google Play.');
                if(!$this->verifyGooglePlay($transaction)){
                    $error_response['error']['message'] = 'Verify Google Play Failed.';
                    return $error_response;
                }
                break;
        }

        return $success_response;
    }

    /**
     * Verify App Store receipt
     * @param array $transaction
     * @return boolean
     */
    private function verifyAppStore($transaction){
        // endpoint
        $production_url = 'https://buy.itunes.apple.com/verifyReceipt';
        $sandbox_url = 'https://sandbox.itunes.apple.com/verifyReceipt';

        $params = [
            'verify' => false,
            'headers' => [
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
            ],
            'json' => [
                'receipt-data' => $transaction['appStoreReceipt'],
            ],
        ];

        $http_client = new Client();

        // Production
        try {
            Log::info('Send iOS receipt production.');
            $response = $http_client->request('POST', $production_url, $params);
            if($response->getStatusCode() !== 200) {
                Log::notice('Response not 200 OK.');
                return false;
            }
            $body = json_decode($response->getBody()->getContents(), true);
            Log::info($body);
        }catch(ClientException $e) {
            Log::error($e->getMessage());
            return false;
        }

        // Sandbox
        if($body['status'] === 21007) {
            Log::info('Send iOS receipt sandbox');
            try {
                $response = $http_client->request('POST', $sandbox_url, $params);
                if($response->getStatusCode() !== 200) {
                    Log::notice('Response not 200 OK.');
                    return false;
                }

                $body = json_decode($response->getBody()->getContents(), true);
                Log::info($body);
            }catch(ClientException $e) {
                Log::error($e->getMessage());
                return false;
            }
        }

        if ($body['status'] !== 0) {
            Log::notice('Receipt status not 0.');
            return false;
        }

        // Check bundle id
        if ($body['receipt']['bundle_id'] !== $this->bundle_id) {
            Log::notice('Invalid bundle id.');
            return false;
        }

        return true;
    }

    /**
     * Verify Google Play receipt
     * @param array $transaction
     * @return boolean
     */
    private function verifyGooglePlay($transaction){
        $receipt = $transaction['receipt'];
        $signature = $transaction['signature'];

        // RSA public key generation
        $pubkey = openssl_get_publickey($this->pubkey);

        // Base64 decode signature
        $signature = base64_decode($signature);

        // Signature verification
        $result = (int)openssl_verify($receipt, $signature, $pubkey, OPENSSL_ALGO_SHA1);
        if($result !== 1){
            Log::notice('Signature invalid.');
            return false;
        }
        openssl_free_key($pubkey);

        // Check package name
        $receipt = json_decode($transaction['receipt']);
        if($receipt->packageName !== $this->bundle_id) {
            Log::notice('Invalid package id.');
            return false;
        }

        return true;
    }
}
.env
BUNDLE_ID=com.example.billingtest
PRODUCT_IDS=coins100,coins200
PUBKEY="-----BEGIN PUBLIC KEY-----
****************************************************************
****************************************************************
****************************************************************
****************************************************************
****************************************************************
****************************************************************
********
-----END PUBLIC KEY-----"

仕様通りできていればcordova-plugin-purchaseから呼び出せるはずです。

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

YOUは何処から日本へ?(ページにアクセスしたユーザーがどこから来たか調べる)

アクセスしたユーザーの国や地域、IPアドレス取得

案件で多言語サイトのデフォルト言語をユーザーごとに出し分けたいという要望があったため
フロント、サーバー側で国や地域、IPアドレスを取得する方法を調査した

JavaScripts で対応

JS単体では端末のIPを取得することはできないがAPIを利用することで取得できる
以下は中でもフリーのサービスを抜粋したもの

1. ipinfo.io

 アクセスするとIPを取得して表示してくれるサービス。
 JSON形式で情報が返却される。
 アカウント作成とアクセス回数に上限がある
 ipinfo.io

2. Geolocation API

 WiFi、GPS、IPアドレスなどから位置情報を取得するAPI。
 JSON形式で情報が返却される。
 位置情報の取得にはユーザーの許可が必要なため拒否されれば当然取得できない。
 WiFiや緯度経度からの取得はGoogleが提供していることが多いため中国などでは利用できないことがある。
 Geolocation API

3. ipify

 フリーのIP取得サービス。
 JSON形式で情報が返却出来る。
 上限は無し。
 ipify
 API

4. jsonip.com

 無料無制限のIP取得サービス。
 JSON形式でIPを取得できるがおまけに広告とドキュメントのURLが含まれている。
 以前は政治的メッセージも含まれていたがなくなったらしい。
 上限なし。
 jsonip.com
 API

5. JSON Test

 フリーのIP等取得サービス。
 IPアドレス単体やリクエストヘッダー情報をJSON形式で取得できる。
 名前のせいで情報が見つけにくい。
 JSON Test
 API

6. ipapi.co

 フリーと商用ライセンスのあるIP等取得サービス。
 アカウント登録が必要。
 IPだけでなく国や地域、緯度経度など詳細なデータをJSON形式で取得できる。
 珍しくドキュメントもそろっているので親切。
 ipapi.co

PHP で対応

PHPはIPアドレスを取得できるが国や地域を取得するにはライブラリかAPIが必要になる(一部有料)

1. IPinfoAPI

 IPアドレスを渡すと国や地域のコードをJSON形式で取得できるサービス
 上限が毎秒2回まで。

2. Geo IP2

 MaxMind社から提供されたデータベースファイルをサーバー側に設置しアクセスしたIPで検索してどの国や地域からアクセスがあったかを調べる。
 無償版ライセンスでは国まで、有償版では地域まで取得できるので用途によって使い分け可能。
 Geo IP

AWS で対応

AWSのCloudFront-Viewer-Country はIPアドレスから判断したアクセス元の国コードを返却するためAWSを利用していれば対応可能。
 AWS

まとめ

  • JavaScriptsは単体ではIPを取得できないがAPIを使用すればIPもしくは国コードなどのアクセス元情報が取得できる。
    ただしIPアドレスの取得方法によっては対象外の国や地域があるため注意が必要。

  • PHPはIPアドレスは単体で取得可能だがアクセス元の国や地域のコードを取得するにはライブラリ、APIが必要。
    ライブラリのライセンスは詳細な情報が必要であれば有償版、国コードが必要なだけであれば無償版でよい。

  • その案件がAWSを利用しているのであればCloudFront-Viewer-Country で対応可能

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

WordPressでカスタム投稿タイプを作成し、HTMLに出力する

ワードプレスのテーマtwentytwentyを基盤に、オリジナルのカスタム投稿タイプを作成し、それをトップページの一覧画面にアーカイブ表示をする実装を行ったので、備忘録的にこの記事を残す。

環境情報

PHP:version 7.3.12
WordPress:version 5.5.3
WPテーマ:twentytwenty

カスタム投稿タイプの作成

基盤の作成

投稿の種類にはお知らせや、製品紹介、スタッフ情報などサイトの目的やクライアントの要望によって様々だと思います。そのため要望や機能にあったカスタム投稿を追加する必要があります。

カスタム投稿を追加するには「functions.php」に下記のようば記述をしていきます。今回はイベント情報というカスタム投稿を追加してみます。

functions.php
/*【管理画面】カスタム投稿を追加 */
/* 例)イベント情報を追加 */
function custom_post_type() {
    $labels = array(
        'name' => 'イベント情報', //管理画面に表示する名前
        'add_new' => '新規イベント追加', //新規追加ボタンの名前
        'add_new_item' => '新規イベントを追加', //新規追加ページのタイトル
        'edit_item' => 'イベントを編集', //編集ページのタイトル
        'new_item' => '新規イベント', //一覧ページの「新規追加」ボタンのラベル
        'view_item' => 'イベントを表示', //編集ページの「投稿を表示」ボタンのラベル
        'search_items' => 'イベントを検索', //一覧ページの検索ボタンのラベル
        'not_found' =>  '投稿されたイベントはありません', //一覧ページに投稿が見つからなかったときに表示
        'not_found_in_trash' => 'ゴミ箱にイベントはありません。', //ゴミ箱に何も入っていないときに表示
    );
    $args = array(
        'label' => __( 'Events' ),
        'labels' => $labels,
        'public' => true, //投稿タイプをパブリックにする
        'menu_position' => 4, //メニューに表示される順番
        'supports'=> array( 'title', 'thumbnail', 'excerpt', 'editor' ) //投稿できる内容,
        'menu_icon' => 'dashicons-flag', //アイコンの設定
        'has_archive' => 'event'
    );
    register_post_type( 'event', $args );
}
add_action( 'init', 'custom_post_type' );

第2パラメータ $args の引数(一部抜粋)

wp-add-event-post-1500x821.jpg
wp-add-events-1500x822.jpg
これでカスタム投稿のイベント情報が追加されました。

同じように新着情報や、スタッフ情報などのカスタム投稿を追加することができます。複数の投稿を追加するにはラベルを追加します。

カスタム投稿のアイコンの変更

管理画面に表示させるアイコンは、WordPress公式のdashiconsから選択することができます。ラベルの「menu_icon」を編集してください。

functions.php
'menu_icon' => 'ここにクラスを記述',//アイコンの設定

https://developer.wordpress.org/resource/dashicons/

カスタム投稿ページのplaceholderの変更

次に投稿ページのカスタマイズをしていきます。まずは、タイトルのプレスホルダーを分かりやすい項目に変更しておきます。デフォルトではタイトルを追加になっていますが、今回はイベント名に変更します。

「functions.php」に以下のようなコードを追加します。

functions.php
// playceholderの変更
function title_placeholder_change( $title ) {
    $screen = get_current_screen();
    if ( $screen->post_type == 'events' ) {
        $title = 'イベント名';
    }
    return $title;
}
add_filter( 'enter_title_here', 'title_placeholder_change' );

「$title = ”;」の部分に変更したい文字を入れます。
wp-edit-placeholder-1500x821.jpg
wp-edit-placeholder2-1500x821.jpg
これで「タイトル名」から「イベント名」に変更となりました。

カスタム投稿に入力エリアを追加

次に先ほど追加したカスタム投稿に任意の入力エリアを追加します。

例としてイベント情報にイベント期間を入力する欄を設けます。「functions.php」に追記していきましょう。

functions.php
// 固定カスタムフィールドボックス
function add_event_fields() {
    add_meta_box( 'event_setting', 'イベントの情報', 'insert_event_fields', 'events', 'normal' );
}
add_action( 'admin_menu', 'add_event_fields' );

// カスタムフィールドの入力エリア
function insert_event_fields() {
    global $post;
    //下記に管理画面に表示される入力エリアを作ります。「get_post_meta()」は現在入力されている値を表示するための記述です。
    echo '期間: <input type="text" name="event_date" placeholder="例)4/21~5/21" value="'.get_post_meta( $post->ID, 'event_date', true ).'" size="50"><br>';
}
add_meta_box( $id, $title, $callback, $screen, $context, $priority, $callback_args );
$id
(文字列) (必須) 編集画面セクションの HTML ID
初期値: なし
$title
(文字列) (必須) 編集画面セクションのタイトル、画面上に表示される
初期値: なし
$callback
(callback) (必須) 編集画面セクションに HTML 出力する関数. The function name as a string, or, within a class, an array to call one of the class's methods. The callback can accept up to two arguments: the first argument is the $post object for the post or page that is currently being edited. The second argument is the full metabox item (an array), see Callback args. See the second example under Examples below.
初期値: なし
$screen
(文字列) (オプション) The type of writing screen on which to show the edit screen section (examples include 'post','page','dashboard','link','attachment','custom_post_type','comment' where custom_post_type is the custom post type slug)
初期値: null
$context
(文字列) (オプション) 編集画面セクションが表示される部分 ('normal', 'advanced' または (2.7 以降) 'side')
初期値: 'advanced'
$priority
(文字列) (オプション) ボックスが表示される優先度 ('high', 'core', 'default' または 'low')
初期値: 'default'
$callback_args
(array) (オプション) Arguments to pass into your callback function. The callback will receive the $post object and whatever parameters are passed through this variable.
初期値: null

wp-add-inputarea-1500x821.jpg
これでカスタム投稿ページにイベント期間を入力する項目が追加されました。

カスタム投稿に入力内容を保存

入力するエリアは追加されましたが、入力した値を保存しないといけないのでそのコードを記述します。

functions.php
// カスタムフィールドの値を保存
function save_event_fields( $post_id ) {
    if ( ! empty( $_POST['event_date'] ) ) {
        update_post_meta( $post_id, 'event_date', $_POST['event_date'] ); //値を保存
    } else {
        delete_post_meta( $post_id, 'event_date' ); //値を削除
    }
}
add_action( 'save_post', 'save_event_fields' );

これでカスタム投稿ページにイベント情報・期間の入力エリアが表示され、入力された値を使用することができます。

カスタム投稿一覧ページのレイアウトを変更

カスタム投稿ページで新規にページを追加します。
すると一覧ページは下記のような表示になると思います
wp-events-default-1500x822.jpg
せっかくなので先ほど追加した、イベント期間とアイキャッチをイベント情報一覧ページで表示させます。

functions.php
// イベント期間とアイキャッチの表示
function manage_events_posts_columns( $columns ) {
    $columns[ 'event_date' ] = '期間';
    $columns[ 'thumbnail' ] = 'アイキャッチ';
    return $columns;
}

function add_column( $column_name, $post_id ) {
    if ( $column_name == 'event_date' ) {
        $date = get_post_meta( $post_id, 'event_date', true );
    }
    if ( isset( $date ) && $date ) {
        echo ( $date );
    }

    if ( 'thumbnail' == $column_name ) {
        $thum = get_the_post_thumbnail( $post_id, 'small', array( 'style'=>'width:100px;height:auto;' ) );
    }
    if ( isset( $thum ) && $thum ) {
        echo $thum;
    } else {
        echo __( 'None' );
    }
}

add_filter( 'manage_events_posts_columns', 'manage_events_posts_columns' );
add_action( 'manage_events_posts_custom_column', 'add_column', 10, 2 );

wp-edit-events-1500x822.jpg
これで入力したイベント期間と設定したアイキャッチが一覧ページに表示されました。

HTMLに出力する

先ほど作成したカスタム投稿タイプを、トップページで表示するという例です。

経験上、メインループに影響を与えないサブループ(カスタムループ)で記述しておくほうが安全ですね。

get_posts( )

サブループの記述方法には、「WP_Query( )」と「get_posts( )」がありますが、「WP_Query()」の方がより複雑な処理を行うことができます。

初心者の方には「get_posts( )」の方が優しいので、今回はこちらを使った実装を紹介します。

index.php
<ul>

  <?php
    $event_args = [
      'post_type' => 'event', // カスタム投稿名が「event」の場合
      'posts_per_page' => 5, // 表示する数
    ];
    $event_posts = get_posts( $event_args ); ?>

  <?php if ( $event_posts ) : foreach ( $event_posts as $post ) : setup_postdata( $post ); // 投稿がある場合 ?>

    <!--  ループ開始  -->

    <li>
      <p><?php the_time( 'Y年 n月' ); ?></p>
      <h3><?php the_title( ); ?></h3>
      <?php the_content( ); ?>
    </li>

    <!-- △ ループ終了 △ -->

  <?php endforeach; ?>

  <?php else : // 記事がない場合 ?>

    <li>まだ投稿がありません</li>

  <?php endif; wp_reset_postdata( ); ?>

</ul>

ループで使うテンプレートタグや関数

get_posts( ) を使うことで、サブクエリを発行することができ、任意のデータを表示することができるようになり、ループ処理の中でテンプレートタグや関数が使えるようになります。

get_posts( ) でのループ処理が一通り終わったタイミングで wp_reset_postdata( ) でメインクエリにリセットすることを忘れないようにしましょう。

カスタム投稿の入力内容を表示させる

入力欄を自作した場合

さきほどカスタム投稿のイベント情報に、期間を入力する欄を設けましたが、ここに入力された値をフロントで表示させるには下記のコードを使用します。

index.php
<?php echo get_post_meta($post->ID, 'event_date', true); ?>

プラグインAdvanced Custom Fields( ACF )を使って入力欄を作成した場合

中身をそのまま表示させたい場合とそうでない場合で表現が異なります。

// そのまま表示させたい場合
the_field( 'event_date' );

// 表示させない場合
get_field( 'event_date' );

the_content( ) のpタグを外す

the_content( ) はデフォルトでpタグで囲まれるようになっています。

しかし、場合によってはpタグで囲みたくないこともあります。そんなときは remove_filter( ) 関数を使います。

index.php
<?php
    remove_filter( 'the_content', 'wpautop' );
    the_content( );
?>

Pタグで囲みたくないコンテントが出力されている場所「the_content( )」を探します。
見つけたら the_content( ) の前に remove_filter( 'the_content', 'wpautop' ) を記述し、the_content を wpautop フィルターフック から外してやる。

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

[PHP]require_once

PHPの require_once について

require_once(リクワイア ワンス)は、プログラムファイルを読み込む命令。読み込んだファイルをコードとして実行する。ワンスと付いているので、一度だけ読み込む。

読み込むファイルの拡張子は、何でもいいが、今回はPHPのコードを含んだファイルを読み込むので、phpという拡張子にしている

HTMLテンプレート
<!DOCTYPE html>
<html lang='ja'>
    <head>
        <meta charset='utf-8'>
        <title>PHP-Web - wawawa</title>
        <style>body {padding: 10px;}</style>
    </head>
    <body>
        <h1>Hello templates</h1>
    </body>
</html>
index.php
<?php
  // ここにテンプレートを読み込む
require_once'views/content.tpl.php';
結果
> Hello templates

以上!

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

きっと正しくないレンタルサーバーの作り方 Vol.2 - ざっくりアーキテクト

はじめに

前回の記事からだいぶ時間が経ちましたが、第2回めです(・∀・)。
今回は全体的なアーキテクトについて記述していこうと思います(・∀・)。

※注意!
この一連の記事で紹介するコードは動作の概念を説明するものでありセキュリティーなどは意識していません(・∀・)。

実際に運用するシステムなどに使用しないでください(・∀・)。
(そのまま使うひともいないと思いますが)

また、私も記事を書きながら開発をしていくので「後になってみたら最初の方の記事間違えてたー」なんて事は起きそうです(・∀・)。

ご了承ください(・∀・)。

目次

全体の構成

基本的に単一のサーバーで動作しますが、後から複数台構成に移行しやすいような構成を目指します(・∀・)。

アカウント管理

rentaserve.com では
- 管理者アカウント(以降「アカウント」と表記)
- ユーザーアカウント(以降「ユーザー」と表記)
の二種類のアカウントがあります(・∀・)。

どちらもレンタルサーバーを使用するユーザーが使用するアカウントですが、ちょっと意味合いが異なります(・∀・)。

例えば

https://mao.rentaserve.com

というサイトを作ったとしたら、maoアカウント と呼びます(・∀・)。

そして

taro@mao.rentaserve.com
jiro@mao.rentaserve.com
saburo@mao.rentaserve.com

のようなメール / FTP アカウントを作成した場合、taro や jiro を ユーザー と呼びます(・∀・)。

rentaserve.com では

  • アカウントを LDAP(ActiveDirectory)で管理
    • ActiveDirectory には Samba 4 AD を使用
  • ユーザーを mariadb で管理

することにします(・∀・)。
LDAPを採用することにより、サーバーを複数台にした時にアカウントを一元管理しやすいためです(・∀・)。
各サーバーにドメインコントローラーを設置して連携させることで、他のサーバーにアカウントを追加しても他のサーバーに反映されるように出来るためです(・∀・)。

また、アカウントのパスワードの秘匿性が高いという利点もあります(・∀・)。
(ActiveDirectory はたとえ管理者でもパスワードのハッシュ値さえ見れない仕様のため)

rentaserve.com システムの本体からは LDAP 関数を使用してアカウントの操作をします(・∀・)。

ウェブサーバー

単純に Apache2.4 を使用します(・∀・)。
php-fpm を使用して PHP スクリプトを実行できるようにします(・∀・)。

特に Apache 固有の機能などは使用しないため、別に nginx 等でも構いません(・∀・)。

サブドメイン毎に設定ファイルを作成し、php-fpm の Unix ソケット経由で PHP を実行します(・∀・)。

セキュリティー確保のため、ユーザー毎に Unix ソケットを作成して PHP を実行します(・∀・)。

PHP

前述した通り、7.3 を使用します(・∀・)。

php-fpm 用の設定を各ユーザーごとに作成して、そこにユーザー固有の設定を書くことによって xdebug などを設定可能にします(・∀・)。
また、
- exec など使い方によってはセキュリティー事故の危険性が高い関数を禁止する
- LDAP系の関数を使用禁止にする
- chroot を使用してホームディレクトリ以下までしかアクセスできないようにする
- php-fpm の実行ユーザーをアカウントユーザーと同一にする(www-data などで実行しない)
のような細工をしておきます(・∀・)。

FTP

ProFTPd を使用します(・∀・)。
ユーザーは mariadb で管理し、ドメイン単位でアクセス先のディレクトリを制限します(・∀・)。
ドメイン単位でのアクセス制限とは、例えば mao というアカウントを作成したとして、そのアカウントが独自ドメイン「example.com」を取得した場合、

というような意味です(・∀・)。
別に一緒くたにしたり混在したりした管理も可能ですが、基本的に
「そのドメインのひとがそのドメインのウェブサイトを管理するだろうなー」
という方針のため、このような仕様にします(・∀・)。

また、FTPS(FTP over SSL)でのアクセスのみ可能とします(・∀・)。
生の FTP はセキュリティー的にアレなので使用させない方向で考えています(・∀・)。

独自ドメインを設定したユーザーの場合、FTPクライアントで証明書関連の警告が出るかも

メール

Postfix / Dovecot を使用します(・∀・)。

これもユーザー管理に mariadb を使用します(・∀・)。
メールは IMAP のみサポートし、POP3 はサポートしないことにします(・∀・)。
(特に理由はない)

こちらも IMAPS / SMTPS といった TLS 通信のみ許可する方向で考えています(・∀・)。

ファイヤーウォール

ufw などを使用します(・∀・)。
あんまり考えてはいません(・∀・)w

サーバー管理

今回は考えません(・∀・)。

興味ある人は Zabbix などを入れてみるとかどうでしょう(・∀・)。
(投げやり)

外部公開DNS

今回は PowerDNS を使用します(・∀・)。
(bind は使いにくい)

PowerDNS 採用の理由は
- レコードを mariadb で管理できる
- Debian/GNU Linux 10 標準のパッケージなので apt で簡単に使用できる
ためです(・∀・)。

ドメイン

とりあえず お名前.com を使用しています(・∀・)。
他のレジストラでも良いと思いますが、よく分かっていません(・∀・)w

独自ドメインについては rentaserve.com を権威DNSサーバーとして動作させ、ネームサーバーに rentaserve.com(ns1.rentaserve.com とか ns2.rentaserve.com とか作って)アクセスさせることで実現させるつもりです(・∀・)。

独自ドメインの設定時にネームサーバーが *.rentaserve.com ではない場合に弾くような仕組みです(・∀・)。

証明書

みんな大好き Let's Encrypt を使用します(・∀・)。

*.rentaserve.com のワイルドカードドメインを取得するためちょっと細工が必要そうです(・∀・)。

最後に

つらつらと長くなりましたが、次回から実際にサーバーを構築していこうと思います(・∀・)。

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

【Laravel8.16】ORMで指定したカラムだけ取得する方法

Laravel歴2日の初心者です。
間違えてたらごめんなさい。

指定したカラムだけ取って来たい!!どないすんねん!!ってなったときの備忘録です。
公式マニュアルにも書いておらず、ググっても答えが出てこなかったのでソース直読みすることに。

クエリビルダとORMでの全件取得方法

クエリビルダ
$users = DB::table('users')->get();
ORM
$users= App\Models\Users::all();

指定したカラムだけ取得する方法

クエリビルダ
$users = DB::table('users')->select('name', 'email as user_email')->get();
ORM
$users= App\Models\Users::all('name', 'email as user_email');

ソース

Model.php
    public static function all($columns = ['*'])
    {
        return static::query()->get(
            is_array($columns) ? $columns : func_get_args()
        );
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHP] エラー解決 npm 脆弱性の解決

前回のエラーを解決する際に脆弱性が2つあるよというエラーに初遭遇。
前回の記事のURLはこちらになります。

(省略)
found 2 vulnerabilities (1 low, 1 high)
run `npm audit fix` to fix them, or `npm audit` for details
(省略)

上記のエラーを突破しましたのでその備忘録として投稿します。

解説

まず何が脆弱性なのかを確認するためターミナルでnpm auditを実行する

//ターミナルにて
npm audit  

すると下記のようなリストがターミナルで出現します。
“スクリーンショット” 2020-12-01 13.45.10.jpg

今回はSEMVER WARNING: Recommended action is a potentially breaking change
というエラーが2つ出てきました。ここでターミナルでnpm audit fixを実行しましょう。

//ターミナルにて
npm audit fix 

しかし、下記の画像のようにまだエラーを修復できませんでした。
“スクリーンショット” 2020-12-01 13.56.07.jpg
これでも突破できる方法がnpm audit fix --forceというものがあります。
--forceはオプションになり強制的に修復させるというやり方です。

//ターミナルにて
npm audit fix --force 

これを実行したら無事に下記の画像のように脆弱性がなくなりました。
“スクリーンショット” 2020-12-01 14.03.05.jpg

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

英語版リーダブルコード(The Art of Readable Code)を読んで日本語訳で要点を殴り書きしてみた。

【最初に】:point_up:
英語版リーダブルコード(The Art of Readable Code)を読んで日本語訳で要点を殴り書きしてみた。

英語からの翻訳なのでやや口語的でない文章になりますが、
我慢して読んで頂けると幸いです。

【1】  理解しやすいコード

コードは理解しやすくなければならない。
コードは他の人が最短期間で理解できるように書かなければならない。
コードは短くしたほうがいい。だけど「理解するまでにかかる時間」を短くするほうが大切

【2】  名前に情報を詰め込む

•Choosing specific words
明確な単語を選ぶこと。
Get → FetchやDownload。

•Avoiding generic names (or knowing when to use them)
「tmp」や「retval」の汎用的な名前を使わない。

tmp - その変数において一時的である事が最も重要な時のみに使用する事。

•Using concrete names instead of abstract names
抽象的な名前ではなく、具体的な名前を使用。
ServerCanStart() → CanListenOnPort()

•Attaching extra information to a name, by using a suffix or prefix
接頭辞や接尾辞を追加の情報を名前に付ける。

•Deciding how long a name should be
名前をどれ位長くするか決める事。

•Using name formatting to pack extra information
大文字の使用、アンダースコアなどを使って意味のある方法で使う。
「_」をクラスのメンバで使用して、ローカル変数と区別する。

短い名前は短い範囲ではOK。

(ex)
document → doc
string  → str

【3】  誤解しない名前

「誰かがこの名前で他の意味で誤解しないか?」を自分自身で精査せよ。

max_ or min_ を使う。

Naming Booleans
・is, has, can, or should can  がベター。
・変数に否定の条項を入れない事。

【4】  美学

•Use consistent layout, with patterns the reader can get used to.
•Make similar code look similar.
•Group related lines of code into blocks.

【5】  コメントすべき事を知る

■書くべきでないもの

・コード自体から容易に連想出来る事。
・悪いコードを補足するコメント。それなら悪いコードを直そう。

■記録する必要がる考える

■読み手になって考えてみる。

・コードやコメントのどの部分が読む人に疑問になるか予想する。
・平均的な読者が予期しない驚くべき行動を文書化する。
・読み手がコード迷わないように、コードをブロック毎に要約する。

【6】  Making Comments Precise and Compact

■コメントとコンパクトに

■“it” や “this” が複数のものを指す可能性がある代名詞を避ける

■実用的な関数のコードの正確な振る舞いをコメントせよ

■input/outputの例を用いて説明せよ

■自明の詳細よりもコードの高レベルでの意図を記載せよ

■多くの意味の言葉で簡潔にコメントせよ

【7】  Making Control Flow Easy to Read

■ネストは浅くしよう。

【8】  Breaking Down Giant Expressions

【9】  Variables and Readability

・変数が多くなるほど、追跡が困難に

・変数のスコープが大きくなるほど、その追跡が困難に

・変数の値が変わるほど、現在値の追跡が困難に

【10】  Extracting Unrelated Subproblems

【11】  One Task at a Time

「一度にひとつのタスク」を行う

・動いているタスクをリストに上げる。
関数やクラスに分ける事も出来れば、1つの関数内での論理的な段落になる。

【12】  Turning Thoughts into Code

おばあちゃんに説明出来なければ、完全に理解してるとは言えない。
(アルベルト・アインシュタイン)

・plain English(専門用語抜きの英語)で同僚に説明するように、 コードで説明せよ。

・その説明でキーワードやフレーズに注意を払おう。

・その説明に一致するようにコードを書こう。

【13】  Writing Less Code

最も読みやすいコードはコードを全く書かない事である。

コードベースを出来るだけ小さくしておく事がベスト。

・複製コードは除いて、出来るだけ多くの汎用コードを作成する。

・使ってないコード、役立たずの機能を削除。

■ライブラリに慣れ親しもう

15分かけてライブラリの関数名、モジュール名を読んでみよう。
これはライブラリ全体を覚えるのではなくて、「これ見たことあるぞ!」
と次の機会に生かす事が出来るのである。

【14】  Testing and Readability

■テストしにくいコードの特徴と、どういう問題を引き起こすか

特性 テスト容易性の問題 設計の問題
グローバル変数の使用 テスト毎にグローバル状態をリセットする必要がある。(そうしないと異なるテストでお互いにインターフェースしてしまう) どの関数が影響するか理解しずらい。関数単体で考える事が出来ず、他に影響があれば全体を理解しなければならない。
外部コンポーネント依存するコード セットアップが難しく、テストが難しい。テストを避けるようになる。 依存関係が失敗すると、システムが自体も失敗しやすくなる。変更による影響範囲を理解するのが難しい。クラスのリファクタが難しい。システムが失敗しやすくなり、リカバリの方法も難しい。
非決定論的な振る舞いをするコード テストは当てにならず、信頼出来ない。 プログラムは競合状態、非再現性のバグが発生しやすくなる。プログラムを推理しずらくなり、作成途中のバグは追うのも直すのも難しい。

further reading
参考文献

Code Complete

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

Laravel 8にてRouteの記述につまずいた話

Laravel6以前と8ではRouteのアクションの記述が違う

Laravel6以前の場合

web.php
Route::get('test', 'testController@index');

Laravel8の場合

web.php
Route::get('test', [test::class, 'index']);

まとめ

これらのような記述の違いで頭を悩ましてしまったため、みなさんもネットで情報を得る際は自分の記述しているLaravelのバージョンとネットに記述してあるLaravelのバージョンを比較して、正しい情報を得れるように心がけていきましょう!

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

boolean をカウントする方法(PHP)

PHPバージョン

PHP 8.0.0

したかったこと

チェックリストを作成し、チェックの数によってランクを分ける仕組みを作りたかった。
まだphpを学習して1日しか経っておらず、フレームワークすら触れていない状態で、チェックした場合どのような値が戻ってくるのかがわからなかったため、
チェックした場合:check
チェックしなかった場合:nocheck
という値が返ってくると仮定して考えていきます。

では先に実際に書いたコードを↓

<?php
$k = "check"; //基準の値
$a001 = "check";
$a002 = "nocheck";
$a003 = "check";
$bool1 = ($a001 == $k);
$bool2 = ($a002 == $k);
$bool3 = ($a003 == $k);

$score = $bool1 + $bool2 + $bool3;
echo $score."\n";

if ($score == 0) {
    echo "あなたはDランクです";
} elseif ($score == 1) {
    echo "あなたはCランクです";
} elseif ($score == 2) {
    echo "あなたはBランクです";
} else {
    echo "あなたはAランクです";
} 

構造

チェックされた数を数える方法
bool型が返してくれる値が
真:1
偽:
これを利用していきます。
checkという文字を真と設定して、それぞれのbool値を足していきます。
そうすることで、真のみ1足されていくので、チェックだけ数えることができます。
あとは、チェックの数ごとにif分でランク分けしていけば、ひとまず完成です。

今後

今私は転職活動中で、ポートフォリオを作成しております。
ポートフォリオでは人事評価webアプリを作りたく、その部品づくりの一環として、今回のチェックを数える試作品を作っておりました。
ポートフォリオ完成に近づいたら、もっと無駄のないコードができていると思うので、また再投稿いたします!
 

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

Web エンジニアが始める Server-side Swift "Vapor"

はじめに

image.png
2014年の WWDC で電撃発表された Swift は、もはや iOS アプリ開発では定番となっていますが、Web 開発界隈のエンジニアにとっては「何それ美味しいの?」という感じであり、ましてや「サーバサイドで Swift が動く!」とか言われても、どうせ最低限の事しかできず「プレステで Linux が動く!」くらいのお遊びだと思われている方も一定数いらっしゃるのでは無いでしょうか?(シランケド)

とか言う私も、当時の WWDC の Swift 発表は興奮して朝まで眠れなかったくらいですが、最近は Swift からは少し離れて Web アプリ開発にどっぷりです。

そんなわけで、今回は、Web 開発者の目線で、Swift の Web アプリケーションフレームワーク Vapor を触ってみたいと思います。

Swift ってどんな言語?

2014年以前は、iOS や macOS のアプリは Objective-C という変態言語での開発が主流でした。WWDC での突然の発表で世に出た Swift ですが、普及するには4、5年はかかると見られていた予想とは裏腹に、あっと言う間に多くのプロジェクトが Swift に移行していきました。一つの iOS プロジェクト内で Objective-C と Swift を混在して書けるというバイナリ互換が後押ししたというのもあるでしょう。

さらに翌年にはオープンソース化して GitHub で公開したことによって、他のプラットフォームへの移植のハードルが下がりました。今では Linux だけでなく Windows でも動作するようになっています。

じゃあ Linux や Windows でも iOS のようなアプリが作れるのかというとそうではなく、あくまでも言語の基礎部分だけがオープンソース化しているに過ぎず、スマートフォンの画面を構成する UiKit や、マルチメディアな機能を扱う AVFoundation など iPhone 環境に特化した様々な非公開フレームワーク群が備わった環境が iOS SDK としてデベロッパーにのみ提供されており、Xcode でのアプリ開発を可能にしているわけです。

そんな Swift さんですが、Objective-C という技術的負債をバッサリ捨てたことにより、何の障壁もなく様々なプログラムバラダイムのトレンドを実装できたという背景もあって、クロージャーやジェネリクス、Optional 型、タイプセーフな構文などは他のナウい言語と共通する部分もあって学びやすい言語だと思います。

Vapor とは?

Vapor はそんな Swift で書かれた Web アプリケーションフレームワークです。Laravel (Lumen) にインスパイアされて作ったとか作らなかったとか(すみません以前公式に書かれてた気がしましたがソースが見つかりませんでした)。Web アプリケーション開発では一般的な MVC の構成で、DB接続 (PostgreSQL / MySQL / SQLite / MongoDB) はもちろん、ORM やテンプレートエンジンなどの Web に必須となる機能もエコシステムとして提供されています。

Vapor は今のところ Mac と Linux で利用可能です。Mac だと Homebrew、Linux では yum や apt などでパッケージが提供されています。あくまでも実行環境ということなので docker や Homestead を使えば Windows でも開発は可能です。

image.png

余談ですが、Swift 製の Web アプリケーションフレームワークと言えば当初は IBM が開発する Kitura が有名でしたが、調べたら2020年1月に(IBMとして)開発を終了しているようでした。

サーバサイドを Swift で書くメリット

Swift で iOS アプリを開発しているエンジニアにとっては、馴染みの無い PHP や Ruby に手を出してサーバサイドの実装を行うことは大きなハードルになりますが、サーバサイドも Swift で書けるということで、システム全体を得意言語でカバーできることになります。何より頭の中で言語のコンテキストをスイッチすることが無くなると言うメリットは大きいでしょう。Web エンジニア風に言えば、シングルページアプリケーションを JavaScript で書きつつ、サーバ API は node.js で実装するような「JavaScript ボーダレス」な開発環境と言えば分かりやすいんじゃないでしょうか。

では、PHP などの非コンパイル言語での開発を生業としているエンジニアにとって、Swift でサーバサイドのプログラムを実装するメリットは何でしょうか?
Swift を使ったことがなければ答えは「メリットなし」でしょう。餅は餅屋。得意言語で実装するに越したことはないです。ただ、少しでも Swift を触ったことがあれば、(もしくはこれから学ぼうという意欲があれば)、読みやすい、書きやすいで定評のある Swift を使うメリットはあると思います。

個人的には Xcode での開発ができる点もメリットだと思います。Web 開発の IDE は Electron や Javas ベースのものがほとんどで、Web 開発者の多くは、メモリ食い過ぎ問題や動作もっさり問題に悩まされています。 macOS ネイティブで快適に安定動作する IDE は皆無でした。賛否はありますが、Xcode が使えるのは開発効率的にもメリットだと思います。

環境準備

言語比較やIDEの批評をすると石が飛んでくるらしいので早速 Vapor のインストールに入りましょう。
まずは事前に下記をインストール。

Xcode は初回立ち上げ時に License Agreement に同意しないと Vapor のインストールで怒られるので同意しておいてください。Developer Program への登録は不要なので、AppStore からインストールしてください。

インストール

Vapor 本体は Homebrew でインストールします。下記コマンドで一発です。

$ brew install vapor

完了したら vapor コマンドが使用できるので、バージョンが表示されればOKです。

$ vapor --version
framework: 4.36.0

プロジェクトの作成

では早速 Web アプリケーションを作成しましょう。ここでは ApiTest というプロジェクトを作成します。vapor new コマンドでプロジェクト名を渡します。

$ vapor new ApiTest
Cloning template...
name: ApiTest
Would you like to use Fluent? (--fluent/--no-fluent)
y/n>

Fluent を使用するかどうかを聞かれるので、y か n を入力。
Fluent は Laravel で言うところの Eloquent のようなライブラリみたいです。とりあえず y で入れておきます。

Which database would you like to use? (--fluent.db)
1: Postgres (Recommended)
2: MySQL
3: SQLite
4: Mongo

Fluent を使用すると言うことはその後ろにデータベースがあるわけですので、次でデータベース製品を選択します。PostgreSQL が Recomended なので、1 を入力。

Would you like to use Leaf? (--leaf/--no-leaf)
y/n>

次は Leaf の追加。こちらは Laravel で言うところの Blade テンプレートシステムです。もちろん y を入力。

すると完了メッセージと共に下記のようなアスキーアートが表示されます。

image.png

アスキーアートと言えば、昔アドベントカレンダーで書いた自分の記事を思い出しました。全然関係ないけど。

Xcode の起動

プロジェクトディレクトリに移動して下記コマンドで Xcode が起動します。

$ cd cd ApiTest
$ vapor xcode

初回起動時は依存パッケージがダウンロードされるのでしばらく待って、左上のビルドボタン(再生ボタン)がアクティブになるまで待ってください。
image.png

依存バッケージのダウンロードが完了したら、ビルドを実行してみましょう。ショートカットキー「⌘ + R」で実行できます。
初回は時間がかかりますが、完了すると Xcode のコンソールに下記のようなメッセージが表示されます。

[ WARNING ] No custom working directory set for this scheme, using /Users/shigeta/Library/Developer/Xcode/DerivedData/ApiTest-awtfxnyehqlqwxclpfyylflkbavb/Build/Products/Debug
[ NOTICE ] Server starting on http://127.0.0.1:8080

ちょうど npm run dev したような感じですね。ではブラウザでアクセスしましょう。

image.png

軽く構成を眺める

初期構成のファイル構成を軽く見てみましょう。
image.png
既視感があると言うかなんと言うか、もうフォルダ名のファイル名で大体わかっちゃいますよね。主要なファイルをさくっと紹介。

Resources/Views/index.leaf

Leafのテンプレートファイルです。Webエンジニアであれば説明は不要でしょう。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">

  <title>#(title)</title>
</head>

<body>
  <h1>#(title)</h1>
</body>
</html>

Sources/App/Controllers/TodoController.swift

こちらはコントローラーです。
予め TODO の CRUD 遷移が実装されています。

import Fluent
import Vapor

struct TodoController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let todos = routes.grouped("todos")
        todos.get(use: index)
        todos.post(use: create)
        todos.group(":todoID") { todo in
            todo.delete(use: delete)
        }
    }

    func index(req: Request) throws -> EventLoopFuture<[Todo]> {
        return Todo.query(on: req.db).all()
    }

    func create(req: Request) throws -> EventLoopFuture<Todo> {
        let todo = try req.content.decode(Todo.self)
        return todo.save(on: req.db).map { todo }
    }

    func delete(req: Request) throws -> EventLoopFuture<HTTPStatus> {
        return Todo.find(req.parameters.get("todoID"), on: req.db)
            .unwrap(or: Abort(.notFound))
            .flatMap { $0.delete(on: req.db) }
            .transform(to: .ok)
    }
}

Migrations/CreateTodo.swift

マイグレーションファイルもありますね。Swift 知らなくても Laravel や Rails 使ってる人であれば直感的に理解できるんじゃないでしょうか。

import Fluent

struct CreateTodo: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("todos")
            .id()
            .field("title", .string, .required)
            .create()
    }

    func revert(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("todos").delete()
    }
}

Models/Todo.swift

モデルクラスです。

import Fluent
import Vapor

final class Todo: Model, Content {
    static let schema = "todos"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "title")
    var title: String

    init() { }

    init(id: UUID? = nil, title: String) {
        self.id = id
        self.title = title
    }
}

Sources/App/routes.swift

URLルート定義ファイル。URLのマッピングをここに記載。これも Web アプリケーションフレームワークではおなじみですね。

import Fluent
import Vapor

func routes(_ app: Application) throws {
    app.get { req in
        return req.view.render("index", ["title": "Hello Vapor!"])
    }

    app.get("hello") { req -> String in
        return "Hello, world!"
    }

    try app.register(collection: TodoController())
}

まとめ

Vapor はざっと見た感じ Rails や Laravel に近い構成なので、Web アプリケーションエンジニアにとっては取っ付きやすいのでは無いでしょうか。

Swift は言語的にもとてもスマートで、本当に洗練された無駄の無い言語なので、iOS アプリだけで使うのは勿体無いですが、とは言え、PHP などの軽量言語のエンジニアにとってはまだまだ敷居が高いです。特にブラウザ上で動作するメイン言語がまだまだ JavaScript 全盛なので、Web アプリエンジニアにとってはあまりメリットが感じられないかもしれません。

そうなると願いは一つですね。

いつの日か、Swift がブラウザ上で動作する時代が来ますように!

告知

猫会社 猫会社で有名な qnote は今年もアドベントカレンダーに参加しています!
応援よろしくお願いします!

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

Amazon DynamoDBでphpのセッションを管理する

はじめに

AWS DynamoDBでphpのセッションを管理します。
マルチAZの場合、複数のEC2でセッション情報を同期する必要があります。

その場合、候補に上がるのが、 DynamoDBかElastiCacheになるはずです。

1. IAMポリシーの作成
EC2インスタンスにアタッチしているロールに、このポリシーを割り当てます。

php-sessions
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:Scan",
                "dynamodb:BatchWriteItem"
            ],
            "Resource": "arn:aws:dynamodb:ap-northeast-1:{AWSのアカウントID}:table/{DynamoDBのテーブル名}"
        }
    ]
}

2. DynamoDBの作成
DynamoDBを作成します。

項目
テーブル名 任意
プライマリキー id (文字列)

テーブル作成後、「概要->TTLの管理」から、 TTLを有効化にします。

項目
TTL属性 expired (任意)

3. EC2インスタンスにphpのSDKをインストールする
EC2インスタンスにcomposerを利用して、phpのSDKをインストールします。

:スクリプト
# composer設定
EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"

if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
    >&2 echo 'ERROR: Invalid installer signature'
    rm composer-setup.php
    exit 1
fi

php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php

sudo mv composer.phar /usr/local/bin/composer
sudo yum install -y --enablerepo=remi,remi-php70 php-xml
sudo rm composer*
composer require aws/aws-sdk-php

4. phpからの利用方法
セッション管理用のクラスを作成します。
シングルトンパターンを利用します。

SessionDynamoDB.php
<?php

# 任意のディレクトリに配置したautoload.phpを呼び出す
require_once(dirname(__FILE__) . '/autoload.php');

use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\SessionHandler;
use Aws\DynamoDb\Exception\DynamoDbException;

interface SessionDynamoDBInterface
{
    //インスタンスを生成する。
    public static function getInstance();

    //ハンドラーを登録する。
    public function start();
}

class SessionDynamoDB implements SessionDynamoDBInterface
{

    private static $singleton;
    private $client;
    private $sessionHandler;

    /**
     * DynamoDBクライアント生成
     */
    private function __construct()
    {
        try {
            $this->client = DynamoDbClient::factory([
                'region' => 'ap-northeast-1',
                'version' => 'latest',
            ]);

            $this->sessionHandler = SessionHandler::fromClient($this->client, [
                'table_name'               => '{DynamoDBのテーブル名}',
                'hash_key'                 => 'id',
                'session_lifetime'         => 3600,
                'consistent_read'          => true,
                'locking'                  => false,
                'batch_config'             => [],
                'max_lock_wait_time'       => 10,
                'min_lock_retry_microtime' => 5000,
                'max_lock_retry_microtime' => 50000,
            ]);
            $this->sessionHandler->register();

            // Start the session
            session_start();

            // // Close the session (optional, but recommended)
            session_write_close();

        } catch(DynamoDbException $e){
            throw new DynamoDbException ($e->getMessage());
        }
    }

    /**
     * インスタンスを生成する。
     *
     * @return object self::$singleton
     */
    public static function getInstance(): object
    {
        if (!isset(self::$singleton)) {
            self::$singleton = new SessionDynamoDB();
        }
        return self::$singleton;
    }

    /**
     * このインスタンスの複製を許可しないようにする
     *
     * @throws RuntimeException
     */
    public final function __clone()
    {
        throw new RuntimeException ('Clone is not allowed against ' . get_class($this));
    }

    /**
     * ハンドラーを登録する。
     *
     */
    public function start(): void
    {
        $this->sessionHandler->register();

        // Start the session
        session_start();
    }

}

クラスからセッションを利用します。

php実行ファイル
# 任意のディレクトリに配置したSessionDynamoDB.phpを呼び出す
require_once(dirname(__FILE__) . '/SessionDynamoDB.php');

$session = SessionDynamoDB::getInstance();
$session->start();

# 冒頭に上記の記述をすれば、あとは普通に$_SESSIONを利用するだけです。
var_dump($_SESSION);

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

一体いつから PHPがセミコロン省略できないと錯覚していた?

・ヮ・)あ、おはようございまーす
PHPでセミコロン書いてますか?

PHPはCで実装されていてJavaに強く影響を受けているためか、行末にセミコロンを付けないといけません。

Python、Go、Swift、kotlin、Ruby、JavaScript などセミコロンを書かない(または省略してもいい)言語を使っている方からは
しばしば白い目で見られがちです(諸説あり):sob:

正直、悔しいです…:rage:

PHPの真の力(笑) を見せつけます!

【朗報】PHPにはセミコロンを省略できる構文が2つある:tada:

if

1つ目は if 文 です
PHPの if文 は、戻り値があるようなものであれば 変数だろうが、関数であろうが、 式でもいれることができます

$var = 0;
if ($var) {} // 変数はもちろんOK
if (fn() => 0) {} // クロージャー、アロー関数ももちろんOK
if ($var = 1) {} // 代入式でもOK

しかし 戻り値が無いような式は入れることができません:joy:

if (echo 'は戻り値がないのでだめ!!') {} // ただし print は戻り値があるからOK

if (function hoge() {}) {} // 関数の宣言はだめ

if (use Foo\Baz\Bar) {} // use 式もだめ

// もちろん for 文 はif 文 の条件には入れられません
for ($i = 0; $i < 10; ++$i) {}

?>

2つ目は ?>(閉じタグ) です
PHPには、?> の直前のみセミコロンを省略できる仕様があります

<?= 'こういう場合は' ?>
<?php echo '省略可能' ?>

これはHTMLに埋め込むときだけでなく、PHPのコードの中にも埋め込むことができます:clap:

<?php
function printLink(string $text, string $href) { ?>
    <!-- ここはHTML -->
    <a href="<?= $href ?>"><?= $text ?></a>
<?php }

// さらにこういうこともOK
printLink('りんく', 'https://example.com')?><?php

つまり ?><?php (閉じタグと開始タグのセット) はセミコロンであり、これを使うことでセミコロンを省略できると言えます(省略とは言えない説もあり)

セミコロン書かないでコーディングしてみる

簡単な文法だけだとつまらないので複雑めにFizzBuzz書きました

<?php

namespace FizzBuzz\Handler {

    abstract class Handler
    {
        public static function handle(int $value, callable $next): string
        {
            return static::isMatched($value) ? static::toString() : $next($value)?><?php
        }

        abstract public static function isMatched(int $value): bool?><?php // ここら辺IDEがエラー吐きがち
        abstract public static function toString(): string?><?php
    }

    final class FizzHandler extends Handler
    {

        public static function isMatched(int $value): bool
        {
            return $value % 3 === 0?><?php
        }

        public static function toString(): string
        {
            return 'Fizz'?><?php
        }
    }
    final class BuzzHandler extends Handler
    {

        public static function isMatched(int $value): bool
        {
            return $value % 5 === 0?><?php
        }

        public static function toString(): string
        {
            return 'Buzz'?><?php
        }
    }
    final class FizzBuzzHandler extends Handler
    {

        public static function isMatched(int $value): bool
        {
            return FizzHandler::isMatched($value) && BuzzHandler::isMatched($value)?><?php
        }

        public static function toString(): string
        {
            return FizzHandler::toString().BuzzHandler::toString()?><?php
        }
    }
}

namespace FizzBuzz {
    use FizzBuzz\Handler\FizzHandler?><?php
    use FizzBuzz\Handler\BuzzHandler?><?php
    use FizzBuzz\Handler\FizzBuzzHandler?><?php
    use Generator?><?php

    class FizzBuzz
    {
        private $handlerClassList = [
            FizzBuzzHandler::class,
            FizzHandler::class,
            BuzzHandler::class,
        ]?><?php

        public function __invoke(): Generator
        {
            $handler = fn($i) => (string)$i?><?php
            foreach (array_reverse($this->handlerClassList) as $handlerClass) {
                $handler = fn(int $i) => $handlerClass::handle($i, $handler)?><?php
            }
            // for 文の中のセミコロンもばっちり
            for ($i = 1?><?php $i <= 100?><?php ++$i) {
                yield $handler($i)?><?php
            }
        }
    }
}

namespace Main {
    use FizzBuzz\FizzBuzz?><?php

    foreach ((new FizzBuzz)() as $v) {
        echo $v, PHP_EOL?><?php
    }
}


※文法によってはIDEがエラーを吐くかもしれませんが実行できます

PHPは黙ってセミコロン書いたほうがいいですね

なんでこんな物を書こうと思ったのか不明です

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

[PHP]標準入力で与えられる値を取得する

PHPで標準入力で与えられる値を取得する

PHP
<?php
$input = fgets(STDIN);
echo $input;
?>

Rubyの場合

Ruby
input = gets
print input

PHPはRubyと違いコード量が多くなる。 <?php ?>タグで囲む事でPHPのコードとして認識される

以上!

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

Laravel 全テーブルをTrancateしてからシーディングを行う方法

Laravel Advent Calendar 2020 - Qiita の 1日目 の記事です。

環境

  • PHP: 7.4.12
  • Laravel: 8.16.1
  • MySQL: 8.0.21
  • doctrine/dbal: 3.0.0

予備知識

Laravel Seeding(シーディング) とは

Laravelにて標準搭載されている初期データ(テストデータ)を登録する機能です。

Laravel Migration(マイグレーション) とは

Laravelにて標準搭載されているデータベースのテーブル定義の変遷のバージョン管理をしてくれる機能です。

困ったこと

Laravel migrate:fresh コマンド

migrate:fresh コマンドはすべてのテーブルをドロップして、すべてのマイグレーションを再度実行してくれるコマンドです。

$ php artisan migrate:fresh

# --seed オプションを付けるとシーディングの実行まで行ってくれます。
$ php artisan migrate:fresh --seed

マイグレーションはプロジェクトの開発が進めば進むほど、履歴が長くなってしまいまっさらな状態を作るためにマイグレーションファイルをすべて適応するのはどんどん長くなってきてしまいます。

ダカーポを使ったり、Laravel8で新しく実装されたマイグレーションスカッシングを使う手もありますが、もっと手軽に実行できる方法が我々には求められていました。

Laravel db:seed コマンド

Laravelのシーディングのみ行ってくれるコマンド。

$ php artisan db:seed

テストデータが入った状態でシーディングし直そうとするも、古いレコードが残ったり、外部キー制約でうまく削除できない問題が起こる。

$ php artisan migrate:fresh --seed

で解決するが、マイグレーション適用時間がかかってしまう。
やはり、このコマンドだけでデータをクリアする方法が我々には求められていた。

解決方法

全テーブルを truncate するシーダーを作ろう。

$ composer require doctrine/dbal

doctrine/dbal ライブラリが必要です。
テーブル定義を変更する場合には必須なライブラリなので、この機能を求める頃にはおそらく入っているはず...

$ php artisan make:seeder TruncateAllTables

生成された database/seeds/TruncateAllTables.php ファイルを下記のように上書きします。

database/seeds/TruncateAllTables.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;

class TruncateAllTables extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(): void
    {
        Schema::disableForeignKeyConstraints();

        foreach ($this->getTargetTableNames() as $tableName) {
            DB::table($tableName)->truncate();
        }

        Schema::enableForeignKeyConstraints();
    }

    /**
     * @return array
     */
    private function getTargetTableNames(): array
    {
        $excludes = ['migrations'];
        return array_diff($this->getAllTableNames(), $excludes);
    }

    /**
     * @return array
     */
    private function getAllTableNames(): array
    {
        return DB::connection()->getDoctrineSchemaManager()->listTableNames();
    }
}

やってることはシンプルで全テーブル名を取得して各テーブルでtruncateして削除しています。
migrations テーブルを消す訳には行かないので除外してます。

外部キー制約が張られてあるとレコードを削除できない場合があるので一時的に無効化してます。
テーブル削除後に外部キー制約を有効化してます。

作成した TruncateAllTables クラスを DatabaseSeeder クラスに登録します。

database/seeds/DatabaseSeeder.php
<?php declare(strict_types=1);

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run(): void
    {
        $this->call(TruncateAllTables::class);

        // シーダーをたくさん書く ...
        \App\Models\User::factory(10)->create();
    }
}

コマンドを実行すればokです。

$ php artisan db:seed

Seeding: Database\Seeders\TruncateAllTables
Seeded:  Database\Seeders\TruncateAllTables (153.14ms)
Database seeding completed successfully.

これでシーディングを実行しても古いレコードが残ったり、外部キー制約で怒られることもなくなりました。我々の完全勝利だ!??
ちなみに TruncateAllTables だけ呼び出したい場合はクラス名をオプション指定して実行すればokです。

$ php artisan db:seed --class=TruncateAllTables

Database seeding completed successfully.

参考

補足: Laravel7 以前のコード例

Laravel8の場合、名前空間が付いてるのでLaravel7以前で利用する場合は注意が必要です。

database/seeders/DatabaseSeeder.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run(): void
    {
         $this->call(TruncateAllTables::class);

         $this->call(UserSeeder::class);
    }
}
database/seeds/TruncateAllTables.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class TruncateAllTables extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(): void
    {
        Schema::disableForeignKeyConstraints();

        foreach ($this->getTargetTableNames() as $tableName) {
            DB::table($tableName)->truncate();
        }

        Schema::enableForeignKeyConstraints();
    }

    /**
     * @return array
     */
    private function getTargetTableNames(): array
    {
        $excludes = ['migrations'];
        return array_diff($this->getAllTableNames(), $excludes);
    }

    /**
     * @return array
     */
    private function getAllTableNames(): array
    {
        return DB::connection()->getDoctrineSchemaManager()->listTableNames();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 全テーブルをTruncateしてからシーディングする方法

Laravel Advent Calendar 2020 - Qiita の 1日目 の記事です。
明日は @yoshikyoto さんの記事です!

環境

  • PHP: 7.4.12
  • Laravel: 8.16.1
  • MySQL: 8.0.21
  • doctrine/dbal: 3.0.0

予備知識

Laravel Seeding(シーディング) とは

Laravelにて標準搭載されている初期データ(テストデータ)を登録する機能です。

Laravel Migration(マイグレーション) とは

Laravelにて標準搭載されているデータベースのテーブル定義の変遷のバージョン管理をしてくれる機能です。

困ったこと

Laravel migrate:fresh コマンド

migrate:fresh コマンドはすべてのテーブルをドロップして、すべてのマイグレーションを再度実行してくれるコマンドです。

$ php artisan migrate:fresh

# --seed オプションを付けるとシーディングの実行まで行ってくれます。
$ php artisan migrate:fresh --seed

マイグレーションはプロジェクトの開発が進めば進むほど、履歴が長くなってしまいまっさらな状態を作るためにマイグレーションファイルをすべて適応するのはどんどん長くなってきてしまいます。

ダカーポを使ったり、Laravel8で新しく実装されたマイグレーションスカッシングを使う手もありますが、もっと手軽に実行できる方法が我々には求められていました。

Laravel db:seed コマンド

Laravelのシーディングのみ行ってくれるコマンド。

$ php artisan db:seed

テストデータが入った状態でシーディングし直そうとするも、古いレコードが残ったり、外部キー制約でうまく削除できない問題が起こる。

$ php artisan migrate:fresh

で解決するが、マイグレーション適用時間がかかってしまう。
やはり、このコマンドだけでデータをクリアする方法が我々には求められていた。

解決方法

全テーブルを truncate するシーダーを作ろう。

$ composer require doctrine/dbal

doctrine/dbal ライブラリが必要です。
テーブル定義を変更する場合には必須なライブラリなので、この機能を求める頃にはおそらく入っているはず...

$ php artisan make:seeder TruncateAllTables

生成された database/seeds/TruncateAllTables.php ファイルを下記のように上書きします。

database/seeds/TruncateAllTables.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;

class TruncateAllTables extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(): void
    {
        Schema::disableForeignKeyConstraints();

        foreach ($this->getTargetTableNames() as $tableName) {
            DB::table($tableName)->truncate();
        }

        Schema::enableForeignKeyConstraints();
    }

    /**
     * @return array
     */
    private function getTargetTableNames(): array
    {
        $excludes = ['migrations'];
        return array_diff($this->getAllTableNames(), $excludes);
    }

    /**
     * @return array
     */
    private function getAllTableNames(): array
    {
        return DB::connection()->getDoctrineSchemaManager()->listTableNames();
    }
}

やってることはシンプルで全テーブル名を取得して各テーブルでtruncateして削除しています。
migrations テーブルを消す訳には行かないので除外してます。

外部キー制約が張られてあるとレコードを削除できない場合があるので一時的に無効化してます。
テーブル削除後に外部キー制約を有効化してます。

作成した TruncateAllTables クラスを DatabaseSeeder クラスに登録します。

database/seeds/DatabaseSeeder.php
<?php declare(strict_types=1);

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run(): void
    {
        $this->call(TruncateAllTables::class);

        // シーダーをたくさん書く ...
        \App\Models\User::factory(10)->create();
    }
}

コマンドを実行すればokです。

$ php artisan db:seed

Seeding: Database\Seeders\TruncateAllTables
Seeded:  Database\Seeders\TruncateAllTables (153.14ms)
Database seeding completed successfully.

これでシーディングを実行しても古いレコードが残ったり、外部キー制約で怒られることもなくなりました。我々の完全勝利だ!??
ちなみに TruncateAllTables だけ呼び出したい場合はクラス名をオプション指定して実行すればokです。

$ php artisan db:seed --class=TruncateAllTables

Database seeding completed successfully.

参考

補足: Laravel7 以前のコード例

Laravel8の場合、名前空間が付いてるのでLaravel7以前で利用する場合は注意が必要です。

database/seeders/DatabaseSeeder.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run(): void
    {
         $this->call(TruncateAllTables::class);

         $this->call(UserSeeder::class);
    }
}
database/seeds/TruncateAllTables.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class TruncateAllTables extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(): void
    {
        Schema::disableForeignKeyConstraints();

        foreach ($this->getTargetTableNames() as $tableName) {
            DB::table($tableName)->truncate();
        }

        Schema::enableForeignKeyConstraints();
    }

    /**
     * @return array
     */
    private function getTargetTableNames(): array
    {
        $excludes = ['migrations'];
        return array_diff($this->getAllTableNames(), $excludes);
    }

    /**
     * @return array
     */
    private function getAllTableNames(): array
    {
        return DB::connection()->getDoctrineSchemaManager()->listTableNames();
    }
}

補足: 速度のお試し

  • マイグレーションファイル数: 45
  • テーブル数: 24

上記の環境だと約2.5倍ほどの速度差が見られました。
もし実際に試した人がいたらコメント欄で実行時間教えてもらえると嬉しいです。

$ time php artisan migrate:fresh

real    0m5.013s
user    0m0.327s
sys 0m0.178s

約5.0秒

$ time php artisan db:seed --class=TruncateAllTables

real    0m2.274s
user    0m0.273s
sys 0m0.145s

約2.2秒

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

Laravel 全テーブルをTruncateしてからシーディングを行う方法

Laravel Advent Calendar 2020 - Qiita の 1日目 の記事です。
明日は @yoshikyoto さんの記事です!

環境

  • PHP: 7.4.12
  • Laravel: 8.16.1
  • MySQL: 8.0.21
  • doctrine/dbal: 3.0.0

予備知識

Laravel Seeding(シーディング) とは

Laravelにて標準搭載されている初期データ(テストデータ)を登録する機能です。

Laravel Migration(マイグレーション) とは

Laravelにて標準搭載されているデータベースのテーブル定義の変遷のバージョン管理をしてくれる機能です。

困ったこと

Laravel migrate:fresh コマンド

migrate:fresh コマンドはすべてのテーブルをドロップして、すべてのマイグレーションを再度実行してくれるコマンドです。

$ php artisan migrate:fresh

# --seed オプションを付けるとシーディングの実行まで行ってくれます。
$ php artisan migrate:fresh --seed

マイグレーションはプロジェクトの開発が進めば進むほど、履歴が長くなってしまいまっさらな状態を作るためにマイグレーションファイルをすべて適応するのはどんどん長くなってきてしまいます。

ダカーポを使ったり、Laravel8で新しく実装されたマイグレーションスカッシングを使う手もありますが、もっと手軽に実行できる方法が我々には求められていました。

Laravel db:seed コマンド

Laravelのシーディングのみ行ってくれるコマンド。

$ php artisan db:seed

テストデータが入った状態でシーディングし直そうとするも、古いレコードが残ったり、外部キー制約でうまく削除できない問題が起こる。

$ php artisan migrate:fresh

で解決するが、マイグレーション適用時間がかかってしまう。
やはり、このコマンドだけでデータをクリアする方法が我々には求められていた。

解決方法

全テーブルを truncate するシーダーを作ろう。

$ composer require doctrine/dbal

doctrine/dbal ライブラリが必要です。
テーブル定義を変更する場合には必須なライブラリなので、この機能を求める頃にはおそらく入っているはず...

$ php artisan make:seeder TruncateAllTables

生成された database/seeds/TruncateAllTables.php ファイルを下記のように上書きします。

database/seeds/TruncateAllTables.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;

class TruncateAllTables extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(): void
    {
        Schema::disableForeignKeyConstraints();

        foreach ($this->getTargetTableNames() as $tableName) {
            DB::table($tableName)->truncate();
        }

        Schema::enableForeignKeyConstraints();
    }

    /**
     * @return array
     */
    private function getTargetTableNames(): array
    {
        $excludes = ['migrations'];
        return array_diff($this->getAllTableNames(), $excludes);
    }

    /**
     * @return array
     */
    private function getAllTableNames(): array
    {
        return DB::connection()->getDoctrineSchemaManager()->listTableNames();
    }
}

やってることはシンプルで全テーブル名を取得して各テーブルでtruncateして削除しています。
migrations テーブルを消す訳には行かないので除外してます。

外部キー制約が張られてあるとレコードを削除できない場合があるので一時的に無効化してます。
テーブル削除後に外部キー制約を有効化してます。

作成した TruncateAllTables クラスを DatabaseSeeder クラスに登録します。

database/seeds/DatabaseSeeder.php
<?php declare(strict_types=1);

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run(): void
    {
        $this->call(TruncateAllTables::class);

        // シーダーをたくさん書く ...
        \App\Models\User::factory(10)->create();
    }
}

コマンドを実行すればokです。

$ php artisan db:seed

Seeding: Database\Seeders\TruncateAllTables
Seeded:  Database\Seeders\TruncateAllTables (153.14ms)
Database seeding completed successfully.

これでシーディングを実行しても古いレコードが残ったり、外部キー制約で怒られることもなくなりました。我々の完全勝利だ!??
ちなみに TruncateAllTables だけ呼び出したい場合はクラス名をオプション指定して実行すればokです。

$ php artisan db:seed --class=TruncateAllTables

Database seeding completed successfully.

参考

補足: Laravel7 以前のコード例

Laravel8の場合、名前空間が付いてるのでLaravel7以前で利用する場合は注意が必要です。

database/seeders/DatabaseSeeder.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run(): void
    {
         $this->call(TruncateAllTables::class);

         $this->call(UserSeeder::class);
    }
}
database/seeds/TruncateAllTables.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class TruncateAllTables extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(): void
    {
        Schema::disableForeignKeyConstraints();

        foreach ($this->getTargetTableNames() as $tableName) {
            DB::table($tableName)->truncate();
        }

        Schema::enableForeignKeyConstraints();
    }

    /**
     * @return array
     */
    private function getTargetTableNames(): array
    {
        $excludes = ['migrations'];
        return array_diff($this->getAllTableNames(), $excludes);
    }

    /**
     * @return array
     */
    private function getAllTableNames(): array
    {
        return DB::connection()->getDoctrineSchemaManager()->listTableNames();
    }
}

補足: 速度のお試し

  • マイグレーションファイル数: 45
  • テーブル数: 24

上記の環境だと約2.5倍ほどの速度差が見られました。
もし実際に試した人がいたらコメント欄で実行時間教えてもらえると嬉しいです。

$ time php artisan migrate:fresh

real    0m5.013s
user    0m0.327s
sys 0m0.178s

約5.0秒

$ time php artisan db:seed --class=TruncateAllTables

real    0m2.274s
user    0m0.273s
sys 0m0.145s

約2.2秒

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

PHP8.0がリリースされたので新機能全部やる

2020/11/26にPHP8.0.0がリリースされました

ということで、UPGRADINGに載っている機能や変更点をだいたい全部見て回ることにします。

Backward Incompatible Changes

後方互換性のない変更。
なお、ここで削除される機能の多くは何年も前から公知されています

PHPコア

match is now a reserved keyword.

matchが予約語になりました。
match構文の導入に伴う措置です。

function match(){}

// PHP8.0
Parse error: syntax error, unexpected token "match", expecting "("
// PHP7.4
エラーは起こらない

Assertion failures now throw by default.

assertのデフォルト動作が例外になりました。

assert(false);

// PHP8.0
Fatal error: Uncaught AssertionError: assert(false)
// PHP7.4
Warning: assert(): assert(false) failed

PHP7.4以前と同じ動作に戻すにはini設定assert.exception=0を設定します。

ちなみにPHP7.0以降、アサート文はコンパイル時に除去されて実行コストが0になるので、気軽に突っ込んでおけます。
便利なのでどんどん使いましょう。

Removed ability to call non-static methods statically.

静的でないメソッドを静的に呼び出すことができなくなりました。

class HOGE{
    public function fuga(){}
}
HOGE::fuga();

// PHP8.0
Fatal error: Uncaught Error: Non-static method HOGE::fuga() cannot be called statically
// PHP7.4
Deprecated: Non-static method HOGE::fuga() should not be called statically

これまでは警告は出るものの実行自体はできていました。
今後は致命的エラーになります。

Removed (unset) cast.

(unset)キャストが削除されました。

$a = 1;
(unset)$a;

// PHP8.0
Fatal error: The (unset) cast is no longer supported
// PHP7.4
Deprecated: The (unset) cast is deprecated

今後は普通にunset($a);としましょう。

Removed track_errors ini directive.

ini設定track_errorsが削除されました。

これは何かというと$php_errormsgです。
track_errorsが有効になっていると、最後に発生したエラーメッセージが$php_errormsgに登録されるという謎機能です。
デフォルトはずっとオフで、またPHP7.2でE_DEPRECATEDになったので使っている人もほとんどいないでしょうが、今後はerror_get_lastを使いましょう。

Removed the ability to define case-insensitive constants.

defineの第三引数$case_insensitiveが削除されました。

define('HOGE', 'FUGA', true);
var_dump(HOGE, HoGe);

// PHP8.0
Warning: define(): Argument #3 ($case_insensitive) is ignored
// PHP7.4
FUGA, FUGA // Deprecated: define(): Declaration of case-insensitive constants is deprecated

trueにすると定数が大文字小文字を区別しなくなるという意味のわからない機能です。

PHPは大文字小文字を区別するかしないかはわりかしややこしいのですが、関数/メソッドは区別しない、それ以外は区別する、という認識でだいたい大丈夫です。
実装上は、一律して区別するものだと考えておいたほうが楽でしょう。

Access to undefined constants now always results in an Error exception.

未定義定数にアクセスすると致命的エラーが発生します。

define('HOGE', 'FUGA', true);
var_dump(HOGE, HoGe);

// PHP8.0
Fatal error: Uncaught Error: Undefined constant "HOGE"
// PHP7.4
HOGE // Warning: Use of undefined constant HOGE - assumed 'HOGE'

これまではE_WARNINGを出したうえで、定数名と同じ文字列が割り当てられていましたが、この手抜きができなくなります。

Removed ability to specify an autoloader using an __autoload() function.

__autoload関数が削除されました。

function __autoload($class){}

// PHP8.0
Fatal error: __autoload() is no longer supported, use spl_autoload_register() instead
// PHP7.4
Deprecated: __autoload() is deprecated, use spl_autoload_register() instead

削除されたといっても完全に消えたわけではなく、定義することもできないという特別扱いです。

今後はspl_autoload_registerを使いましょう。
Composerのオートロードとか使った方がもっといいですが。

Removed the $errcontext argument for custom error handlers.

set_error_handlerの第1引数$error_handlerの第5引数$errcontextが削除されました。

set_error_handler(function($errno, $errstr, $errfile, $errline, $errcontext){
    echo $errcontext['fuga'];
});

function hoge(){
    $fuga = 'piyo';
    $y = '1a' + 1;
}
hoge();

// PHP8.0
Fatal error: Uncaught ArgumentCountError: Too few arguments to function {closure}(), 4 passed and exactly 5 expected
// PHP7.4
"piyo"

エラーが発生した個所のローカル変数が配列で入ってくるという謎機能です。

Removed create_function().

PHPの魔関数のひとつcreate_functionが削除されました。

$a = create_function('$a', 'echo $a;');
$a('hoge');

// PHP8.0
Fatal error: Uncaught Error: Call to undefined function create_function
// PHP7.4
"hoge" // Deprecated: Function create_function() is deprecated

かわりに無名関数を使いましょう。
無名関数はPHP5.3から使えるので、古のソースを熟成させながら使い続けてでもいないかぎり、いまどきcreate_functionはそうそう出てこないはずです。

Removed each()

eachが削除されました。

$arr = [1, 2, 3];
while ([$key, $val] = each($arr)) {
    var_dump($key, $val);
}

// PHP8.0
Fatal error: Uncaught Error: Call to undefined function each()
// PHP7.4
0,1 1,2 2,3 // Deprecated: The each() function is deprecated

この関数を使う意味は全くないので、普通にforeach使いましょう

Removed ability to unbind $this from closures

クロージャ内で$thisのバインドを外すことができなくなりました。
とあるのだけどよくわかりません。

$closure = Closure::bind(null, new stdClass());

// PHP8.0
Fatal error: Uncaught TypeError: Closure::bind(): Argument #1 ($closure) must be of type Closure, null given
// PHP7.4
Warning: Closure::bind() expects parameter 1 to be Closure, null given

これは何の意味があるんだ?

Removed ability to use array_key_exists() with objects

オブジェクトに対してarray_key_existsを使えなくなりました。

array_key_exists(1, (object)[0, 1]);

// PHP8.0
Fatal error: Uncaught TypeError: array_key_exists(): Argument #2 ($array) must be of type array, stdClass given
// PHP7.4
Deprecated: array_key_exists(): Using array_key_exists() on objects is deprecated

property_existsを使えってずっと前から言われていたので、今さらこれに引っかかることはないでしょう。

Made the behavior of array_key_exists() regarding the type of the key parameter consistent with isset() and normal array access.

array_key_existsの引数のキー値の挙動が、issetなどと同じになりました。

array_key_exists([], [1]);

// PHP8.0
Fatal error: Uncaught TypeError: Illegal offset type
// PHP7.4
Warning: array_key_exists(): The first argument should be either a string or an integer

isset([], [1]); // こちらは昔からFatal error

これまでは引数のキー値に配列やオブジェクトなど不正な値を突っ込むことができましたが(当然falseなので意味はない)、今後はissetなどと同様のチェックが入ります。

negative_array_index

配列の自動採番キーがマイナスに対応します。

$a = [-10=>1];
$a[] = 2;

// PHP8.0
$a[-9]=>2になる
// PHP7.4
$a[0]=>2になる

これは正直、どうして賛成多数なのかわからない。

The default error_reporting level is now E_ALL.

ini設定error_reportingのデフォルトがE_ALLになります。
これまではE_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATEDでした。

この変更により、適当に書いてるコードは動かなくなります。

echo $a;

// PHP8.0
Warning: Undefined variable $a
// PHP7.4
何も出ない

PHP7.4で何も出ないのは単にデフォルトが非表示になっているだけなので、E_ALLに設定変更すればエラーが出ます。

もちろんerror_reportingを変更すれば黙殺することもできますが、やめましょう。

display_startup_errors is now enabled by default.

ini設定display_startup_errorsの初期値がオンになりました。

Using "parent" inside a class that has no parent will now result in a fatal compile-time error.

親クラスの無いクラスでparentを書くとコンパイルエラーになります。

class A{
  public function __construct(){
    parent::__construct();
  }
}

// PHP8.0
Fatal error: Cannot use "parent" when current class scope has no parent
// PHP7.4
Deprecated: Cannot use "parent" when current class scope has no parent

PHP7.4では、呼ばれないかぎりは警告にとどまります。
もちろんnew A()を実行したら、parentが存在しないため致命的エラーになります。

The @ operator will no longer silence fatal errors

エラー制御演算子@が致命的エラーには効かなくなりました。

@require("notfound.php");

// PHP8.0
Fatal error: Uncaught Error: Failed opening required 'notfound.php'
Process exited with code 255.
// PHP7.4
Process exited with code 255.

もっとも致命的エラーが起きたらスクリプトは強制終了されるので、結果は特に変わりません。

Following the hash comment operator # immediately with an opening bracket is not supported as a comment

#[はコメントにならなくなりました。

#[ これはコメント

// PHP8.0
Parse error: Unclosed '[' 
// PHP7.4
エラーは起きない

アトリビュートの実装に伴う変更です。
[以外の文字はこれまでどおりコメントです。
空白を入れた# [もコメントになります。

LSP violations will now always generate a fatal error

LSP違反となる継承は常に致命的エラーを発生します。

class C1 {
    public function method(array $a) {}
}
class C2 extends C1 {
    public function method(int $a) {}
}

// PHP8.0
Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a)
// PHP7.4
Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a)

これまでは場合によってE_FATALだったりE_WARNINGだったりしていたので、これを揃えたものです。

The precedence of the concatenation operator has changed

連結演算子の優先度が変更されます。

$errors_count = 0;
echo 'unknown_upgrade_error_' . $errors_count + 1;

// PHP8.0
unknown_upgrade_error_1
// PHP7.4
1

これまで.+の優先順位が同じで前から順だったため、直感的ではない結果になることがありました。
今後は.の優先順位が下がります。

Arguments with a default value that resolves to null at runtime will no longer implicitly mark the argument type as nullable

引数のデフォルト値にnullが指定されている場合、引数の型は暗黙的にnull許容型になります。

function test(int $arg = null){}
test(null);

型引数はintですが、デフォルト値にnullが指定されているので暗黙的に?int型となります。
このようにnullを直接渡した場合の挙動はPHP8でも変わりません。

しかし、デフォルト値を定数などで間接的に渡した場合は暗黙のnull許容型が許されなくなります。

const CONST_RESOLVING_TO_NULL = null;
function test(int $arg = CONST_RESOLVING_TO_NULL){}
test(null);

// PHP8.0
Fatal error: Uncaught TypeError: test(): Argument #1 ($arg) must be of type int, null given
// PHP7.4
エラーは出ない

一件ややこしいですが、まあnull許容型の意味を考えれば妥当な変更かなと。

A number of warnings have been converted into Error exceptions

多くのE_NOTICEがE_WARNINGに、多くのE_WARNINGが例外にと、警告レベルが厳しくなります

$a = new stdClass();
$a->b;

// PHP8.0
Warning: Undefined property: stdClass::$b
// PHP7.4
Notice: Undefined property: stdClass::$b


$a = 1;
$a->b++;

// PHP8.0
Fatal error: Uncaught Error: Attempt to increment/decrement property "b"
// PHP7.4
Warning: Attempt to increment/decrement property 'b' of non-object

横着せずに正しいコードを書くようにしましょう。

Attempting to assign multiple bytes to a string offset will now emit a warning

マルチバイト文字列にオフセットアクセスするとE_WARNINGが出るようになります。

<?php
$str = 'あいうえお';
$str[2] = 'か';

// PHP8.0
Warning: Only the first byte will be assigned to the string offset
// PHP7.4
何も出ない

なんにしろ結果は文字化けするので、このようなコードを書いている時点でおそらくバグでしょう。

Unexpected characters in source files will now result in a ParseError

ヌルバイトなど、ソースコード中に想定外の文字コードが入っているとパースエラーになります。
これまではE_COMPILE_WARNINGでした。

Uncaught exceptions now go through "clean shutdown"

"destructors will be called after an uncaught exception"、すなわちキャッチされない例外が発生してもデストラクタが呼ばれるようになる、と読めるのですが、でも実際は前から呼ばれてるんですよね。
なんのことかよくわかりません。

class HOGE{
    public function fuga(){
        throw new \Exception();
    }

    public function __destruct(){
        echo 'destruct called.';
    }
}
(new HOGE())->fuga();

// PHP8も7.4も
destruct called.
Fatal error: Uncaught Exception

Compile time fatal error "Only variables can be passed by reference" has been delayed until runtime

参照渡し引数に値を渡した際の警告タイミングが変更になりました。

try{
  array_pop([1,2,3]);
}catch(\Throwable $e){
  echo $e->getMessage();
}

// PHP8.0
array_pop(): Argument #1 ($array) cannot be passed by reference
// PHP7.4
Fatal error: Only variables can be passed by reference

PHP7.4まではコンパイル時にエラーが出ていたので、プログラム側で制御することができませんでした。
PHP8では評価が実行時まで遅延されるので、結果としてcatchで受け取ることができるようになります。
まあソースコードを修正すべき内容なので、catchできても特に意味はなさそうですが。

Some "Only variables should be passed by reference" notices have been converted to "Argument cannot be passed by reference" exception.

一部の参照渡し引数に値を渡した時の挙動がE_NOTICEから例外になりました。
とあるのですが実例を見つけられませんでした。

array_pop([1,2,3]); // Fatal error
array_pop(array_values([1,2,3])); // Notice: Only variables should be passed by reference

PHP7のころから、値を直接渡した場合はFatal errorとなり、別の関数の返り値をそのまま渡すとE_NOTICEになります。

The generated name for anonymous classes has changed

匿名クラスの名前が変更になりました。

var_dump(new class extends stdClass{});

// PHP8.0
object(stdClass@anonymous)
// PHP7.4
object(class@anonymous)

名前に親クラスやインターフェイスの名前が付くようになり、わかりやすくなります。
しかし、これのせいで互換性が失われる実装って想像もつきませんな。

Non-absolute trait method references in trait alias adaptations are now required to be unambiguous

トレイトに同じ名前がある場合、曖昧さを消さなければならなくなりました。

trait T1{
    public function foo(){}
}
trait T2{
    public function foo(){}
}

class X{
    use T1, T2{
        foo AS bar;
    }
    public function foo(){}
}

// PHP8.0
Fatal error: An alias was defined for method foo(), which exists in both T1 and T2
// PHP7.4
何もエラー出ない

X::foo()はどれを指すのかが曖昧ですが、PHP7.4では暗黙的にT1::foo()とみなされていました。
PHP8.0では以下のように明示しなければなりません。

class X{
    use T1, T2{
        T1::foo AS bar;
    }
}

The signature of abstract methods defined in traits is now checked against the implementing class method

traitに書いた抽象メソッドを実装したときにシグネチャを正しく解析するようになりました。

trait MyTrait {
  abstract public function neededByTrait(): string;
}

class MyClass {
  use MyTrait;
  public function neededByTrait(): int { return 42; }
}

// PHP8.0
Fatal error: Declaration of MyClass::neededByTrait() must be compatible with MyTrait::neededByTrait()
// PHP7.4
何もエラー出ない

今まで異なるシグネチャで実装できていたのがバグなのではという気がしないでもない。

Disabled functions are now treated exactly like non-existent functions

無効化した関数が存在しない関数と同じ扱いになりました。

具体的には、php.inidisable_functions ="exec"と設定したときの結果が異なります。

exec('ls');

// PHP8.0
Fatal error: Uncaught Error: Call to undefined function exec()
// PHP7.4
PHP Warning:  exec() has been disabled for security reasons

PHP7.4までのdisable_functionsは、『存在はしているけど無効化されている』という扱いでした。
PHP8においては単純に『存在しない』となるので、別の定義を行うことも可能です。

data: stream wrappers are no longer writable

dataストリームラッパーに書き込みできなくなりました。

The arithmetic and bitwise operators will now consistently throw a TypeError

各種演算子が、引数に配列、リソース、オブジェクトが渡された場合にTypeErrorを出すようになりました。
対象の演算子は+-*/**%<<>>&|^~++--です。

new stdClass() + 1;

// PHP8.0
Fatal error:  Uncaught TypeError: Unsupported operand types: stdClass + int
// PHP7.4
Notice:  Object of class stdClass could not be converted to number

オブジェクトの場合、GMPのように演算子オーバーロードが設定されている一部のクラスはこれまでどおり計算可能です。

Float to string casting will now always behave locale-independently

浮動小数から文字列へのキャストがロケールに依存しなくなりました。

setlocale(LC_ALL, 'fr-FR');
echo (string)3.14;

// PHP8.0
3.14
// PHP7.4
3,14

フランス語は小数点が','なので、これまでは3,14になっていました。
しかし(float)"3,14"は解釈できず元に戻せなかったりと、わりと中途半端な機能だったので、ロケールに関わらず常に3.14にするようになりました。

Removed support for deprecated curly braces for offset access

中括弧による文字列オフセットアクセスが削除されました。

echo '12345'{3};

// PHP8.0
Fatal error:  Array and string offset access syntax with curly braces is no longer supported
// PHP7.4
4 Deprecated:  Array and string offset access syntax with curly braces is deprecated

何のために存在したのかよくわからない機能です。

Applying the final modifier on a private method will now produce a warning unless that method is the constructor

コンストラクタ以外のprivateメソッドにfinalを書けなくなりました。

class FOO
{
  final private function bar(){}
}

// PHP8.0
Warning:  Private methods cannot be final as they are never overridden by other classes
// PHP7.4
何も出ない

privateメソッドはオーバーライドされることはないよ、というE_WARNINGが出ます。
警告なので動作自体はするのですが、今後動かなくなる可能性もありますし、使わない方がよいでしょう。

If an object constructor exit()s, the object destructor will no longer be called

コンストラクタでexit()した場合、デストラクタが呼ばれなくなりました。

class FOO
{
    public function __construct()
    {
        exit();
    }

    public function __destruct()
    {
        echo 'destruct called.';
    }
}
new FOO();

// PHP8.0
何も出ない
// PHP7.4
destruct called.

元々コンストラクタでthrowしたときはデストラクタが呼ばれていなかったのですが、それと動作を合わせたということらしいです。

Non-strict comparisons between numbers and non-numeric strings now work by casting the number to string and comparing the strings

厳密でない比較演算子の数値比較の挙動が変更になりました。
詳しくはここに書いてあります

var_dump('foo' == 0);

// PHP8.0
false
// PHP7.4
true

厳密な比較演算子の挙動は変更ありません。
===を使っている限りは安泰です。

Namespaced names can no longer contain whitespace

名前空間に空白を入れることができなくなりました。

namespace A \ B{}

// PHP8.0
Parse error:  syntax error, unexpected token "\"
// PHP7.4
エラー出ない

まあ、今までできていたのがおかしいといえばおかしいのですが。

Nested ternaries now require explicit parentheses

曖昧な三項演算子のネストに括弧が必要になりました。

1 ? 2 : 3 ?: 4;

// PHP8.0
Fatal error:  Unparenthesized `a ? b : c ?: d` is not supported
// PHP7.4
Deprecated:  Unparenthesized `a ? b : c ?: d` is deprecated

PHPの三項演算子は他の大半の言語と挙動が異なるという問題がありましたが、それを合わせるための布石です。

debug_backtrace() and Exception::getTrace() will no longer provide references to arguments

debug_backtraceおよびException::getTraceが引数の参照を渡さなくなりました。

function foo(&$bar)
{
    debug_backtrace()[0]['args'][0] = 2;
}

$x = 1;
foo($x);
echo $x;

// PHP8.0
1
// PHP7.4
2

これまでスタックトレース経由で引数を書き替えるという邪悪な操作ができていたのですが、今後はできなくなります。

Numeric string handling has been altered to be more intuitive and less error-prone

数値型文字列の定義が変更になりました

function foo(int $int){}
foo('123abc');

// PHP8.0
Fatal error:  Uncaught TypeError: foo(): Argument #1 ($int) must be of type int
// PHP7.4
Notice:  A non well formed numeric value encountered

PHP7.4までは数値型文字列の定義が『数値型文字列』と『非正規の数値型文字列』の2種類あってわかりにくかったのですが、今後は単純に『数値型文字列』とそれ以外になります。

Magic Methods will now have their arguments and return types checked if they have them declared

マジックメソッドがシグネチャをチェックするようになりました。

class FOO{
    public function __get(array $name):int{
        return 1;
    }
}

// PHP8.0
PHP Fatal error:  FOO::__get(): Parameter #1 ($name) must be of type string when declared
// PHP7.4
エラー出ない

正しい定義に合わせるか、あるいは単にシグネチャを書かないようにすればよいです。

// PHP8.0でもOK
class FOO{
    public function __get($name){
        return 1;
    }
}

call_user_func_array() array keys will now be interpreted as parameter names, instead of being silently ignored

call_user_func_arrayに連想配列を渡したときに、キーが無視されなくなりました。

function foo(...$args){
    var_dump($args);
}
call_user_func_array('foo', ['a'=>1, 'b'=>2]);

// PHP8.0
['a'=>1, 'b'=>2]
// PHP7.4
[1, 2]

そもそもcall_user_func_arrayなんか使うなって話だな。

COM

Removed the ability to import case-insensitive constants from type libraries

COMのタイプライブラリ定数の大文字小文字を区別しない機能が削除されました。
com_load_typelibの第二引数は常にtrueとなり、com.autoregister_casesensitiveも常にonとなります。

ということらしいのだけど、そもそもタイプライブラリって何だ。

Curl

CURLOPT_POSTFIELDS no longer accepts objects as arrays

CURLOPT_POSTFIELDSはオブジェクトを受け取らなくなりました。

$c = curl_init();
curl_setopt($c, CURLOPT_POSTFIELDS, (object)['a'=>1]);

// PHP8.0
Fatal error:  Uncaught Error: Object of class stdClass could not be converted to string
// PHP7.4
エラー出ない

そもそも最初から文字列か配列しか受け取らないという仕様で、オブジェクトを受け取れていたのはたまたまです。
今後は普通に配列を渡せば問題ありません。

Date

mktime() and gmmktime() now require at least one argument

mktimegmmktimeは、最低ひとつの引数が必要になりました。

mktime();

// PHP8.0
Fatal error:  Uncaught ArgumentCountError: mktime() expects at least 1 argument, 0 given
// PHP7.4
Deprecated:  mktime(): You should be using the time()

単に現在時刻がほしいときはtimeを使え、ということらしいです。

DOM

Remove unimplemented classes from ext/dom that had no behavior and contained test data

定義だけあって実装のなかった、あるいは実験的実装だったDOM関連クラスが削除されました。

new DOMTypeInfo();

// PHP8.0
Fatal error:  Uncaught Error: Class "DOMTypeInfo" not found
// PHP7.4
エラー出ない

具体的にはDOMNameList / DomImplementationList / DOMConfiguration / DomError / DomErrorHandler / DOMImplementationSource / DOMLocator / DOMUserDataHandler / DOMTypeInfoが削除されました。
これらはDOMスタンダードからも削除されているため、それと合わせたものとなります。
マニュアルにも元々入ってなかったので影響も極小でしょう。

Enchant

enchant_broker_list_dicts(), enchant_broker_describe() and enchant_dict_suggest() will now return an empty array instead of null

enchant_broker_list_dicts等の関数が、対象がなにもないときにnullではなく[]を返すようになりました。

enchant_broker_init() will now return an EnchantBroker object rather than a resource

enchant_broker_initは、これまでリソースを返していたのがEnchantBrokerオブジェクトを返すようになりました。

他のEnchant関数はこれまでと同じ書き方で透過的に動作します。

enchant_broker_request_dict() and enchant_broker_request_pwl_dict() will now return an EnchantDictionary object rather than a resource

enchant_broker_request_dictenchant_broker_request_pwl_dictは、これまでリソースを返していたのがEnchantDictionaryオブジェクトを返すようになりました。

ていうかEnchantってPHP7.3で削除されるという話だったんだけどどうなったんだ?

Exif

Removed read_exif_data

read_exif_dataが削除されました。

今後はexif_read_dataを使いましょう。

Filter

The FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED flags for the FILTER_VALIDATE_URL filter have been removed

検証フィルタFILTER_VALIDATE_URLにおいて、FILTER_FLAG_SCHEME_REQUIREDFILTER_FLAG_HOST_REQUIREDのフラグが削除されました。

実はプロトコルスキームとホストは元から常に必須だったため、実質的に意味のないフラグでした。

The INPUT_REQUEST and INPUT_SESSION source for filter_input() etc have been removed

filter_inputなどのフラグINPUT_REQUESTとINPUT_SESSIONが削除されました。

そもそも実装されていませんでした。

GD

The GD extension now uses a GdImage objects as the underlying data structure for images, rather than resources

GDリソースがGdImageオブジェクトになりました。

$image = imagecreatetruecolor(100, 100);
$text_color = imagecolorallocate($image, 233, 14, 91);
imagestring($image, 1, 5, 5, 'TEXT', $text_color);
imagepng($image);

echo gettype($image);

// PHP8.0
object
// PHP7.4
resource

この変更は透過的です。
上記コードの$imageはPHP7.4ではリソースであるのに対してPHP8.0ではオブジェクトになりますが、どちらでも同じように動作します。

ただしis_resourceなどでチェックを入れている場合は動かなくなります。

The deprecated function image2wbmp() has been removed

image2wbmp関数が削除されました。

PHP7.3以降E_DEPRECATEでした。

The deprecated functions png2wbmp() and jpeg2wbmp() have been removed

同じく非推奨だったpng2wbmpjpeg2wbmpも削除されました。

The default $mode parameter of imagecropauto() no longer accepts -1.

imagecropautoの第二引数$mode-1を受け付けなくなりました。

$image = imagecreatetruecolor(100, 100);
imagecropauto($image, -1, 0.5);

// PHP8.0
Fatal error:  Uncaught ValueError: imagecropauto(): Argument #2 ($mode) must be a valid mode
// PHP7.4
Deprecated:  imagecropauto(): Crop mode -1 is deprecated

IMG_CROP_DEFAULTなどの定数を使いましょう。

GMP

gmp_random() has been removed

gmp_randomが削除されました。

この関数はプラットフォームに依存して出力が変わるという問題があったため、PHP7.2でE_DEPRECATEDになりました。
かわりにgmp_random_bitsgmp_random_rangeを使いましょう。

Iconv

iconv() implementations which do not properly set errno in case of errors are no longer supported

iconvは、エラー時にerrnoプロパティを適切にセットしない実装はサポートしなくなりました。

と書かれているのですが、errnoがなんなのか一切出てこないので何を見て判断すればよいのかわかりません。

IMAP

The unused default_host argument of imap_headerinfo() has been removed

imap_headerinfoの未使用だった第五引数$defaulthostが削除されました。

The imap_header() function which is an alias of imap_headerinfo() has been removed

imap_headerが削除されました。

imap_headerinfoのエイリアスです。

Intl

The deprecated constant INTL_IDNA_VARIANT_2003 has been removed

定数INTL_IDNA_VARIANT_2003が削除されました。

これはUnicodeから削除されたためです。

The deprecated Normalizer::NONE constant has been removed

Normalizer::NONEが削除されました。

何もしないという意味のない設定です。

The IntlDateFormatter::RELATIVE_FULL, RELATIVE_LONG, RELATIVE_MEDIUM, and RELATIVE_SHORT constants have been added.

IntlDateFormatterクラスに定数RELATIVE_FULL/RELATIVE_LONG/RELATIVE_MEDIUM/RELATIVE_SHORTが追加されました。

追加は互換性のない変更ではないような気がする。

LDAP

The deprecated function ldap_sort / ldap_control_paged_result / ldap_control_paged_result_response has been removed

関数ldap_sortldap_control_paged_resultldap_control_paged_result_responseが削除されました。

かわりにldap_searchを使いましょう。

The interface of ldap_set_rebind_proc has changed

ldap_set_rebind_procの第二引数$callbackが空白""を受け付けなくなりました。

設定を外したい場合はかわりにnullを使います。

Mbstring

The mbstring.func_overload directive has been removed

マルチバイト関数の関数オーバーロード機能が削除されました。

substrを使うと自動的にmb_substrが呼ばれる、といった機能なのですが、事故の元でしかありません。

機能の削除に伴い、定数MB_OVERLOAD_MAILmb_get_infofunc_overloadといった関連項目も削除されます。

mb_parse_str() can no longer be used without specifying a result array

mb_parse_strの第二引数$arrayが必須になりました。

mb_parse_str('a=b');
echo $a;

// PHP8.0
Fatal error:  Uncaught ArgumentCountError: mb_parse_str() expects exactly 2 arguments, 1 given
// PHP7.4
b // Deprecated:  mb_parse_str(): Calling mb_parse_str() without the result argument is deprecated

元々は文字列を直接変数に展開するという危険極まりない関数だったのですが、この変更により配列に展開するという妥当な内容になりました。
$array = mb_parse_str('a=b')とできればもっと良かったのですけどね。

A number of deprecated mbregex aliases have been removed

いくつかのマルチバイト正規表現関数に存在したエイリアスが削除されました。

mbsplit("//u", 'foo');

// PHP8.0
Fatal error:  Uncaught Error: Call to undefined function mbsplit
// PHP7.4
Deprecated:  Function mbsplit() is deprecated

なぜかmb_eregmb_regex_encodingなどには、mberegmbregex_encodingといった_のないエイリアスが存在しました。
これらはマニュアルにも全く存在しない、謎のエイリアスです。

The 'e' modifier for mb_ereg_replace() has been removed

mb_ereg_replaceにおいてオプションeが削除されました。

preg_replaceでは7.0で削除されたのに何故かこっちには残っていたのですが、ようやく揃った形です。

今後はmb_ereg_replace_callbackを使う必要があります。

A non-string pattern argument to mb_ereg_replace() will now be interpreted as a string instead of an ASCII codepoint

mb_ereg_replaceにおいて、第一引数$patternのASCIIコード変換を行わなくなります。

echo mb_ereg_replace(98, 'x', 'abc98');

// PHP8.0
"abcx"
// PHP7.4
"axc98" // Deprecated:  mb_ereg_replace(): Non-string patterns will be interpreted as strings in the future

第一引数に数値を渡した場合、これまでは何故か対応するASCIIコードとして扱われていました。
意味もないうえにわかりにくすぎるので、今後は単純に文字列として扱われます。

The needle argument for mb_strpos(), mb_strrpos(), mb_stripos(), mb_strripos(), mb_strstr(), mb_stristr(), mb_strrchr() and mb_strrichr() can now be empty

mb_strposなどの各関数において、引数$needleに空白が許可されます。

echo mb_strpos('abcde', '');

// PHP8.0
0
// PHP7.4
false // Warning:  mb_strpos(): Empty delimiter

""はあらゆる文字列にマッチするため、だいたい1文字目になります。

The $is_hex parameter, which was not used internally, has been removed from mb_decode_numericentity()

mb_decode_numericentityにおいて、使われていなかった引数$is_hexが削除されました。

The legacy behavior of passing the encoding as the third argument instead of an offset for the mb_strrpos() function has been removed

mb_strrposの、引数$encodingは第四引数ですが、これを第三引数に書いても動く機能が削除されました。

mb_strrpos('b', 'abc', 'UTF-8');

// PHP8.0
Fatal error:  Uncaught TypeError: mb_strrpos(): Argument #3 ($offset) must be of type int, string given
// PHP7.4
Deprecated:  mb_strrpos(): Passing the encoding as third parameter is deprecated

引数の位置が変更されたのはPHP5.2です。
今まで残ってたことにびっくりですね。

The ISO_8859-* character encoding aliases have been replaced by ISO8859-* aliases

サポートされる文字エンコーディングのうち、ISO-8859-xのエイリアスが変更されました。

mb_detect_order(['ISO-8859-1', 'ISO_8859-2']);
var_dump(mb_detect_order());

// PHP8.0
Fatal error:  Uncaught ValueError: mb_detect_order(): Argument #1 ($encoding) contains invalid encoding
// PHP7.4
['ISO-8859-1', 'ISO-8859-2']

これまでISO_8859-xという形のエイリアスが存在したのですが、PHP8.0では使えなくなりました。
かわりにISO8859-xというエイリアスが使えるようになりますが、これはPHP7.4では対応していません。

iconvと形を揃えるためだそうですが、いきなり完全に差し替える必要に陥ります。
とはいえ、最初からエイリアスではなくISO-8859-xの正しい形を使っていれば特に問題ありません。

mb_ereg() and mb_eregi() will now return boolean true on a successful match

mb_eregmb_eregiが、マッチしたらtrueを返すようになりました。

echo mb_ereg('bbb', 'abbbc', $regs);

// PHP8.0
true
// PHP7.4
3

これまでは第三引数$regsの有無によって返り値の値も型も変わるという非常に使いにくいものでした。
この変更によって、マッチしたらtrue、しなかったらfalseとわかりやすい形になります。

OCI8

The OCI-Lob class is now called OCILob, and the OCI-Collection class is now called OCICollection

OCI-LobクラスがOCILobに、OCI-CollectionクラスがOCICollectionに変更されました。

Several alias functions have been marked as deprecated

幾つかの関数のエイリアスが非推奨になりました。

oci_internal_debug() and its alias ociinternaldebug() have been removed

oci_internal_debugが削除されました。

ODBC

odbc_connect() no longer reuses persistent connections

odbc_connectが持続的接続を利用しなくなりました。

よくわかりませんが、odbc_pconnectのほうに同じ dsn、user、 password の組み合わせ (odbc_connect() および odbc_pconnect() による)接続の場合は、 持続的な接続を再利用するとか書いてあるので、おそらく一度odbc_pconnectしたあとにodbc_connectしたら持続的接続になってしまっていたのではないかと思われます。

The unused flags parameter of odbc_exec() has been removed

odbc_execの第三引数$flagsは未使用だったため削除されました。

OpenSSL

openssl_x509_read() and openssl_csr_sign() will now return an OpenSSLCertificate object rather than a resource

openssl_x509_readopenssl_csr_signは、これまでリソースを返していたのをOpenSSLCertificateオブジェクトを返すようになりました。

The openssl_x509_free() function is deprecated

openssl_x509_freeはE_DEPRECATEDとなり、何もしなくなりました。

↑の変更で対象がリソースからオブジェクトになったので、わざわざ関数で開放しなくても自動的に解放されるからです。

openssl_csr_new() will now return an OpenSSLCertificateSigningRequest object rather than a resource

openssl_csr_newもリソースではなくOpenSSLCertificateSigningRequestオブジェクトを返すようになりました。

openssl_pkey_new() will now return an OpenSSLAsymmetricKey object rather than a resource

openssl_pkey_newもOpenSSLAsymmetricKeyオブジェクトを以下略

The openssl_pkey_free() function is deprecated

openssl_pkey_freeはE_DEPRECATEDとなり、何もしなくなりました。

openssl_seal() and openssl_open() now require $method to be passed

openssl_sealopenssl_openの引数$methodが必須になりました。
デフォルト値が'RC4'で、これはセキュアではないためです。

PCRE

When passing invalid escape sequences they are no longer interpreted as literals

無効なエスケープシーエンスが文字列リテラルと解釈されなくなりました。

preg_match('/\i/', 'h\ij', $matches);
var_dump($matches);

// PHP8.0
null // Warning:  preg_match(): Compilation failed: unrecognized character
// PHP7.4
[0=>"i"]

正規表現パターンに存在しない文字列\iは、これまでは単なる文字列と解釈されていましたが、今後は不正なパターンとみなされてエラーが出るようになります。
\iという文字列で検索したい場合は、正しく\\\\iと記述しましょう。

PDO

The default error handling mode has been changed from "silent" to "exceptions"

PDOエラーモードPDO::ATTR_ERRMODEのデフォルト値がPDO::ERRMODE_EXCEPTIONになりました。
これまではPDO::ERRMODE_SILENTでした。

The signatures of some PDO methods have changed

幾つかのメソッドにおいてシグネチャが変更されました。
例としてPDOStatement::setFetchModeは、PDOStatement::setFetchMode(int $mode, ...$params)になります。

PDO_ODBC

The php.ini directive pdo_odbc.db2_instance_name has been removed

pdo_odbc.db2_instance_nameディレクティブが削除されました。

これを設定すると環境変数DB2INSTANCEに値が入るだけというよくわからないディレクティブなので、まあ削除されても妥当でしょう。

pgsql

The deprecated pg_connect() syntax using multiple parameters instead of a connection string is no longer supported

pg_connectにおいて、DSNを使わない形の古い構文が削除されました。

pg_connect("host", "port", "options", "tty", "dbname");

// PHP8.0
Fatal error:  Uncaught ArgumentCountError: pg_connect() expects at most 2 arguments, 5 given
// PHP7.4
エラー出ない

mysqliもPDOも既にDSN形式以外は対応していないので、こちらもいつまでも残しておく必要はないでしょう。

The deprecated pg_lo_import() and pg_lo_export() signature that passes the connection as the last argument is no longer supported

pg_lo_importおよびpg_lo_exportは、実は第三引数にリソースを渡しても動いていたらしいのですが、それが削除されました。

そんな構文ドキュメントにもユーザノートにも全く書かれていないのですが、PHP7.4のソースにはたしかにそれっぽいものが書いてあって、PHP8.0ではそれがなくなっていました。
これ使ってる人とかいるんだろうか。

01.png

pg_fetch_all() will now return an empty array instead of false for result sets with zero rows

pg_fetch_allは、取得行数が0だった場合はfalseを返していたのですが、[]を返すようになりました。

失敗ではなく結果が無いということなので、こちらが妥当でしょう。

Phar

Metadata associated with a phar will no longer be automatically unserialized

これまではPharファイルを読み込もうとした時点で自動的にメタデータが展開されていたのですが、自動的には呼ばれなくなりました。

セキュリティ上の理由です。

Reflection

The method signatures have been changed

幾つかの関数のシグネチャが変更されました。

// PHP7.4
ReflectionClass::newInstance($args)
ReflectionFunction::invoke($args)
ReflectionMethod::invoke($object, $args)

// PHP8.0
ReflectionClass::newInstance(...$args)
ReflectionFunction::invoke(...$args)
ReflectionMethod::invoke($object, ...$args)

The ReflectionType::__toString() method will now return a complete debug representation of the type, and is no longer deprecated

ReflectionType::__toStringは非推奨でなくなりました。

function hoge(?int $a){}
echo (new ReflectionFunction('hoge'))->getParameters()[0]->getType();

// PHP8.0
?int
// PHP7.4
int

また、これまで正確に取ってこれなかったnullableなども取ってこれるようになっています。

Reflection export() methods have been removed

Reflection::exportは削除されました。

ReflectionFunction::export('strpos');

// PHP8.0
Fatal error:  Uncaught Error: Call to undefined method ReflectionFunction::export()
// PHP7.4
Function [ <internal:standard> function strpos ]

ざっくり概要確認するには便利だったんですけどね。

The following methods can now return information about default values of parameters of internal functions

ReflectionParameter::isDefaultValueAvailablegetDefaultValueisDefaultValueConstantgetDefaultValueConstantNameが内部関数にも使えるようになりました。

(new ReflectionFunction('strpos'))->getParameters()[2]->getDefaultValue();

// PHP8.0
0
// PHP7.4
Fatal error:  Uncaught ReflectionException: Cannot determine default value for internal functions

内部関数に対して使う意義はあんまり感じられませんが。

ReflectionMethod::isConstructor() and ReflectionMethod::isDestructor() now also return true

ReflectionMethod::isConstructorおよびisDestructorは、インターフェイスにもtrueを返すようになりました。
これまではインターフェイスには非対応でした。

interface HOGE{
    public function __construct();
}
(new ReflectionFunction('strpos'))->getParameters()[2]->getDefaultValue();

// PHP8.0
true
// PHP7.4
false

ちなみにtraitには昔からtrueを返します。

ReflectionType::isBuiltin() method has been moved to ReflectionNamedType

ReflectionType::isBuiltinReflectionNamedTypeクラスに移動しました。
なぜならReflectionUnionTypeにはビルトインの型はないからです。

Sockets

The deprecated AI_IDN_ALLOW_UNASSIGNED and AI_IDN_USE_STD3_ASCII_RULES flags for socket_addrinfo_lookup() have been removed

socket_addrinfo_lookup関数の第三引数に渡せるフラグAI_IDN_ALLOW_UNASSIGNED/AI_IDN_USE_STD3_ASCII_RULESが削除されました。

glibcで削除されたので追随したということのようです。

socket_create(), socket_create_listen(), etc will now return a Socket object rather than a resource

以下の各関数が、リソースではなくオブジェクトを返すようになりました。
socket_create / socket_create_listen / socket_accept / socket_import_stream / socket_addrinfo_connect / socket_addrinfo_bind / socket_wsaprotocol_info_import

$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_set_option($socket, IPPROTO_IP, IP_MULTICAST_LOOP, 0);
var_dump($socket);

// PHP8.0
object(Socket)#1 (0) {}
// PHP7.4
resource of type (Socket)

socket_set_optionのようなソケットを引数として受け取る関数は、そのまま透過的に動作します。
ただしis_resource等でチェックしている場合は修正が必要です。

socket_addrinfo_lookup() will now return an array of AddressInfo objects rather than resources

socket_addrinfo_lookupは、リソースの配列ではなく、AddressInfoの配列を返すようになりました。

SPL

SplFileObject::fgetss() has been removed

SplFileObject::fgetssは削除されました。

SplHeap::compare($a, $b) now specifies a method signature

SplHeap::compareに型mixedが設定されました。
extendsする場合は型を指定するか、もしくは何も書かないかが必要です。

SplDoublyLinkedList::push() now returns void instead of true

SplDoublyLinkedList::pushがvoidを返すようになりました。

var_dump((new SplDoublyLinkedList())->push(1));

// PHP8.0
null
// PHP7.4
true

これまではtrueを返していました。
元々マニュアルにもvoidと書かれていて、trueを返すのはバグだったみたいです。

SplDoublyLinkedList::unshift() now returns void instead of true

SplDoublyLinkedList::unshiftがvoidを以下同文。

SplQueue::enqueue() now returns void instead of true

SplQueue::enqueueが以下同文。

spl_autoload_register() will now always throw a TypeError on invalid arguments

spl_autoload_registerは、無効な引数について常にTypeErrorを発するようになりました。

spl_autoload_register('HOGE', false);

// PHP8.0
Fatal error:  Uncaught TypeError: spl_autoload_register(): Argument #1 ($callback) must be a valid callback, function "HOGE" not found or invalid function name
// PHP7.4
何も出ない

これに伴い、第二引数$throwは常に無視されるようになります。

SplFixedArray is now an IteratorAggregate and not an Iterator

SplFixedArrayIteratorAggregateをimplementsしました。

var_dump(
    is_subclass_of('SplFixedArray', 'Iterator'),
    is_subclass_of('SplFixedArray', 'IteratorAggregate'),
);

// PHP8.0
false, true
// PHP7.4
true, false

これまではIteratorをimplementsしていました。
また、これに伴ってSplFixedArray::rewindなどIterator由来の実装は削除され、かわりにSplFixedArray::getIteratorが追加されます。

Standard

assert() will no longer evaluate string arguments

assertの引数を文字列表現で渡すことができなくなりました。

assert('$a');

// PHP8.0
true
// PHP7.4
Notice:  Undefined variable: a

PHP7.4までは、引数が文字列のときはPHPコードとして解釈されていました。
つまりevalされていたってことです。

PHP8では'$a'という単なる文字列として扱われます。

parse_str() can no longer be used without specifying a result array

parse_strの第二引数$resultが必須になりました。
上の方にあるmb_parse_strと全く同じ理由です。

fgetss() has been removed

fgetssは削除されました。

The string.strip_tags filter has been removed

文字列フィルタstring.strip_tagsが削除されました。
fgetssなどと同様、何かをするのと同時に文字列をフィルタするのは事故の元なのでやめましょうという方向です。

The needle argument of strpos(), strrpos(), stripos(), strripos(), strstr(), strchr(), strrchr(), and stristr() will now always be interpreted as a string

strposほかの関数で、引数$needleに空文字列が有効になりました。

echo strpos('abc', '');

// PHP8.0
0
// PHP7.4
Warning:  strpos(): Empty needle

空文字はどのような文字列にも該当するので、だいたいは1文字目すなわち0になります。

The length argument for substr(), substr_count(), substr_compare(), and iconv_substr() can now be null

substrsubstr_countsubstr_compareiconv_substrの引数$lengthにnullを渡した時の挙動が変わりました。

echo substr('abcde', 2, null);

// PHP8.0
"cde"
// PHP7.4
""

これまでは返り値が0やら""やらになっていたのですが、これが未指定の場合と同じ動きになります。

The length argument for array_splice() can now be null

array_spliceの引数$lengthにnullを渡した時の挙動が変わりました。

$input = [1, 2, 3, 4];
array_splice($input, 2, null);
var_dump($input);

// PHP8.0
[1, 2]
// PHP7.4
[1, 2, 3, 4]

これまでは何もしませんでしたが、未指定の場合と同じ動きになります。
すなわち、引数$offsetより後ろが全て削除されます。

The args argument of vsprintf(), vfprintf(), and vprintf() must now be an array

vsprintfvfprintfvprintfの引数$argが配列しか受け付けなくなりました。

echo vsprintf('a%sc', 'b');

// PHP8.0
Fatal error:  Uncaught TypeError: vsprintf(): Argument #2 ($values) must be of type array, string given
// PHP7.4
abc

元からマニュアルでは配列のみとなっていたのに、何故か文字列なども受け付けていたという謎のサービスでした。

The 'salt' option of password_hash() is no longer supported

password_hashのオプションsaltが削除されました。
指定する意味はないし固定すると脆弱になるだけなので、元々存在する必要のないオプションです。

The quotemeta() function will now return an empty string if an empty string was passed

quotemetaにから文字列""を渡すと空文字列""を返すようになりました。
これまではfalseでした。

hebrevc() / convert_cyr_string() / money_format() / ezmlm_hash() / restore_include_path() has been removed

以下の関数が削除されました。
hebrevc
convert_cyr_string
money_format
ezmlm_hash
restore_include_path

上位互換が存在する、他の関数と整合性がないなどの理由で、PHP7.4で非推奨となっています。

get_magic_quotes_gpc() and get_magic_quotes_runtime() has been removed

旧時代PHPの黒歴史のひとつ、get_magic_quotes_gpcget_magic_quotes_runtimeが削除されました。
同時に定数FILTER_SANITIZE_MAGIC_QUOTESも削除されました。

PHPはついに、マジッククオートの軛から完全に解き放たれたのです。

Calling implode() with parameters in a reverse order ($pieces, $glue) is no longer supported

implodeは歴史的理由から引数を逆順でも受け付けていたのですが、これが削除されました。

echo implode(['a', 'b', 'c'], '|');

// PHP8.0
Fatal error:  Uncaught TypeError: implode(): Argument #2 ($array) must be of type ?array, string given
// PHP7.4
a|b|c

PHP8.0は、負の遺産の清算が目立ちます。

parse_url() will now distinguish absent and empty queries and fragments

parse_urlの、空のクエリやフラグメントへの返り値が微妙に変わりました。

// 出力のscheme等は省略
var_dump(
    parse_url('http://example.com/foo'),
    parse_url('http://example.com/foo?'),
    parse_url('http://example.com/foo#'),
    parse_url('http://example.com/foo?#'),
);

// PHP8.0
[], ["query"=>""], ["fragment"=>""], ["query"=>"", "fragment"=>""]
// PHP7.4
[], [], [], []

空のクエリには空文字列を出力、クエリ自体がない場合は出力自体行わない、と区別できるようになります。

var_dump() and debug_zval_dump() will now print floating-point numbers using serialize_precision rather than precision

var_dumpdebug_zval_dumpの浮動小数出力が、ini設定precisionではなくserialize_precisionを使うようになりました。

ini_set('precision', 1);ini_set('serialize_precision', 100);
var_dump(1.23456789012345678901234567890123456789012345678901234567890);

// PHP8.0
float(1.2345678901234566904321354741114191710948944091796875)
// PHP7.4
float(1)

100とか指定してもビット数の制限は越えられないみたい。

If the array returned by __sleep() contains non-existing properties, these are now silently ignored

マジックメソッド__sleepに存在しないプロパティを返した場合、無視するようになりました。

class FOO{
    private $a = 'a';
    public function __sleep()
    {
        return ['a', 'b'];
    }
}
echo serialize(new FOO());

// PHP8.0
O:3:"FOO":1:{s:6:"FOOa";s:1:"a";} // Warning:  serialize(): "b" returned as member variable from __sleep() but does not exist
// PHP7.4
O:3:"FOO":2:{s:6:"FOOa";s:1:"a";s:1:"b";N;} // Notice:  serialize(): "b" returned as member variable from __sleep() but does not exist

これまでは、値がnullのプロパティがセットされていました。

The default locale on startup is now always "C"

デフォルトのロケールが常に"C"になりました。
これまではLC_ALLは"C"で、LC_CTYPEは環境変数LANGから取ってくる、みたいなことになっていました。

Removed deprecated DES fallback in crypt()

cryptのSALTフォールバックが削除されました。

echo crypt('str', 'あ');

// PHP8.0
*0
// PHP7.4
UYLTFzPe08. // Deprecated:  crypt(): Supplied salt is not valid for DES

cryptはSALTの形式によって自動的にハッシュ形式を選択するのですが、これまでは未知のSALTが来た場合は自動的にDESにフォールバックしていました。
今後は失敗します。
ただ失敗といっても例外を出したりせず'*0'という文字列を返してくるのでむしろわかりにくいのでは。

Calling crypt() without an explicit salt is no longer supported

cryptの第二引数$saltが必須になりました。

echo crypt('str');

// PHP8.0
Fatal error:  Uncaught ArgumentCountError: crypt() expects exactly 2 arguments, 1 given
// PHP7.4
$1$SsnnMRiS$4clL.zBKekzZvryhRYVUP1 // Notice:  crypt(): No salt parameter was specified

こちらは非対応SALTとは異なり、致命的エラーになります。

Sort comparison functions that return true or false will now throw a deprecation warning

ソート関数でtrue/falseを返している場合、非推奨の警告が出るようになりました。
0と等しいか、0より大きいか、0より小さい整数を返す必要があります。

$arr = [2, 1];
usort($arr, fn($a, $b) => $a > $b);
var_dump($arr);

// PHP8.0
[1, 2] // Deprecated:  usort(): Returning bool from comparison function is deprecated
// PHP7.4
[1, 2]

Any functions accepting callbacks that are not explicitly specified to accept parameters by reference will now warn

リファレンスを受け取らないコールバック関数の引数にリファレンスを書いた場合、警告が出るようになりました。

array_filter([1,2], function (&$v) {
    return true;
});

// PHP8.0
Warning:  {closure}(): Argument #1 ($v) must be passed by reference, value given
// PHP7.4
何も出ない

多くの関数では既にこうなっていたものの、array_filterなど一部はそうなってなかったので追加したということのようです。
それに$vを書き替えても元の値には影響しないので、&を付ける意味は特にないです。

HTTP stream wrapper as used by functions now advertises HTTP/1.1 rather than HTTP/1.0

stream_context_createなどで作れるHTTPストリームラッパーのデフォルトがHTTP1.1になりました。

これまでどおりHTTP1.0を使いたい場合は、コンテキストオプションで指定する必要があります。

$opts = [
    'http'=>[
        'protocol_version'=>'1.0',
    ]
];
$context = stream_context_create($opts);

substr(), mb_substr(), iconv_substr() and grapheme_substr() now consistently clamp out-of-bounds offsets to the string boundary

substrmb_substriconv_substrgrapheme_substrが、範囲外の引数にも文字列を返すようになりました。

var_dump(
    substr('abc', 10)
);

// PHP8.0
""
// PHP7.4
false

これまではfalseになっていました。

Populating $http_response_header variable by the HTTP stream wrapper doesn't force rebuilding of symbol table anymore

HTTPストリームラッパーが$http_response_headerを変更してもシンボルテーブルの再構築が行われなくなりました。

ということらしいけど内部のことはよくわかりません。
きっと詳しい人がなんか教えてくれるはず。

Sysvmsg

msg_get_queue() will now return an SysvMessageQueue object rather than a resource

msg_get_queueがリソースではなくSysvMessageQueueインスタンスを返すようになりました。

Sysvsem

sem_get() will now return an SysvSemaphore object rather than a resource

sem_getがリソースではなくSysvSemaphoreインスタンスを返すようになりました。

The $auto_release parameter of sem_get() was changed to accept bool values rather than int

sem_getの第4引数$auto_releaseの型がintからboolになりました。

Sysvshm

shm_attach() will now return an SysvSharedMemory object rather than a resource

shm_attachがリソースではなくSysvSharedMemoryインスタンスを返すようになりました。

tidy

The $use_include_path parameter has been removed from tidy_repair_string()

tidy_repair_stringの引数$use_include_pathが削除されました。

元々マニュアルにも載ってなかったんだけど、内部的に使っていたらしいです。

tidy::repairString() and tidy::repairFile() became static methods

tidy::repairStringtidy::repairFileが静的メソッドになりました。

tidy::repairString('hoge');

// PHP8.0
エラー出ない
// PHP7.4
Fatal error:  Uncaught Error: Non-static method tidy::repairString() cannot be called statically

Tokenizer

T_COMMENT tokens will no longer include a trailing newline

T_COMMENTトークンは末尾の改行を含まなくなりました。

$str = '<?php // comment
';
var_dump(
    token_get_all($str)[1][1]
);

// PHP8.0
string(10) "// comment"
// PHP7.4
string(11) "// comment
"

Namespaced names are now represented

名前空間はひとまとまりとして解釈されるようになりました。

// 出力は必要部分のみ
$str = '<?php
namespace FOO\BAR;
';
var_dump(
    token_get_all($str)
);

// PHP8.0
[ [T_NAME_RELATIVE, 'FOO\BAR'] ]
// PHP7.4
[ [T_STRING, 'FOO'], [T_NS_SEPARATOR, '\'], [T_STRING, 'BAR'] ]

こちらのほうが名前空間としてわかりやすいですね。

XML

xml_parser_create(_ns) will now return an XmlParser object rather than a resource

xml_parser_createがリソースではなくXmlParserオブジェクトを返すようになりました。

$parser = xml_parser_create();
xml_parse($parser, '<xml></xml>', true);
var_dump($parser);

// PHP8.0
object(XmlParser)#1
// PHP7.4
resource(4) of type (xml)

XMLパーサ関数は、これまでと同じ書き方で透過的に動作します。
またxml_parser_freeは実質的に何もしません。

XMLReader

XMLReader::open() and XMLReader::xml() are now static methods

XMLReader::openXMLReader::XMLは静的メソッドになりました。

XMLWriter

The XMLWriter functions now accept and return, respectively, XMLWriter objects instead of resources

XMLWriter::openUriなどがリソースではなくXMLWriterオブジェクトを返すようになりました。
XMLWriter関数は、これまでと同じ書き方で透過的に動作します。

Zip

ZipArchive::OPSYS_Z_CPM has been removed

定数ZipArchive::OPSYS_Z_CPMが削除されました。

echo ZipArchive::OPSYS_Z_CPM;

// PHP8.0
Fatal error:  Uncaught Error: Undefined constant ZipArchive::OPSYS_Z_CPM
// PHP7.4
9

これ、ZipArchive::OPSYS_CPMの誤字です。

Zlib

gzgetss() has been removed

gzgetssが削除されました。

inflate_init() will now return an InflateContext object rather than a resource

inflate_initがリソースではなくInflateContextオブジェクトを返すようになりました。
inflate_addなどは、これまでと同じ書き方で透過的に動作します。

deflate_init() will now return a DeflateContext object rather than a resource

deflate_initがリソースではなくDeflateContextオブジェクトを返すようになりました。
deflate_addなどは、これまでと同じ書き方で透過的に動作します。

zlib.output_compression is no longer automatically disabled for Content-Type: image/*

ini設定zlib.output_compressionは、これまでは画像に対しては自動的に無効化されていましたが、その自動無効化がなくなりました。

New Features

新機能。

この多くはPHP8.0の新機能で紹介しています。

Core

Added support for union types

UNION型が追加されました。

function hoge(int|string $fuga): bool|stdClass {
    if($fuga === 0){return true;}
    return new stdClass;
}

関数hogeはint型もしくはstring型を受け取り、bool型もしくはstdClassインスタンスを返します。

Added WeakMap

弱いマップです。
メモリが足りなくなったら自動で削除されます。

Added ValueError class

ValueErrorクラスが追加されました。

echo bcadd('1', '2', -1);

// PHP8.0
Fatal error:  Uncaught ValueError: bcadd(): Argument #3 ($scale) must be between 0 and 2147483647
// PHP7.4
3

bcaddの第三引数は小数点以下の桁数なので、マイナス値は不適切です。
このように、型は合っているけど値が範囲外のときに使われる例外となります。

Any number of function parameters may now be replaced by a variadic argument

継承時に、異なる型の値を可変長引数でまとめることができるようになりました。

class A{
    public function hoge(int $foo, string $bar, stdClass $baz){}
}
class B extends A{
    public function hoge(...$args){}
}

// PHP8.0
エラー出ない
// PHP7.4
Warning:  Declaration of B::hoge(...$args) should be compatible with A::hoge

この場合、シグネチャは継承したクラスのものしか確認しません。
すなわち、(new B())->hoge(1, 2, 3)とか書いてもエラーは出ません。

これはいいのか??????

"static" (as in "late static binding") can now be used as a return type

返り値にstaticを書けるようになります。

class Test {
    public function create(): static {
        return new static();
    }
}

使い道がよくわからないので誰か解説よろ。

It is now possible to fetch the class name of an object using $object::class

インスタンスからクラス名を取得できるようになります。

echo (new stdClass)::class;

// PHP8.0
stdClass
// PHP7.4
Fatal error:  Cannot use ::class with dynamic class name

New and instanceof can now be used with arbitrary expressions

Some consistency fixes to variable syntax have been applied

デリファレンス可能範囲が拡大されます。

class Foo{
    const BAR = 'Bar';
}
class Bar{
    public static $baz = 'QUX';
}
echo Foo::BAR::$baz;

// PHP8.0
QUX
// PHP7.4
Parse error:  syntax error, unexpected '::'

これまでは構文エラーになっていたいくつかの構文が許されるようになります。
このRFCややこしくて正直理解が追い付いてません。

Added Stringable interface

Stringableインターフェイスが追加されました。

interface Stringable{
   public function __toString(): string;
}

これがあると__toString()可能になるというものですが、これは暗黙的にimplementsされるため、使用する側としては特に気にする必要はありません。

Traits can now define abstract private methods

traitでabstract privateメソッドが作成可能になりました。

trait FOO {
    abstract private function bar();
}

// PHP8.0
エラー出ない
// PHP7.4
Fatal error:  Abstract function FOO::bar() cannot be declared private

throw can now be used as an expression

throwが式になりました。

fn() => throw new Exception();

// PHP8.0
エラー出ない
// PHP7.4
Parse error:  syntax error, unexpected 'throw'

構文の途中などで気軽に例外を投げられるようになります。

An optional trailing comma is now allowed in parameter lists

関数定義に末尾カンマが許可されました。

function foo($a, $b,){}

// PHP8.0
エラー出ない
// PHP7.4
Parse error:  syntax error, unexpected ')'

関数を呼び出す方はPHP7.3から使えていたので、合わせた対応です。

It is now possible to write catch (Exception) to catch an exception without storing

例外の変数を明示的に受け取らないことができるようになりました。

try{
    new PDO();
}catch(\Throwable){
    echo "なんかエラー出た";
}

// PHP8.0
なんかエラー出た
// PHP7.4
Parse error:  syntax error, unexpected ')'

PDOのエラーメッセージを表示すると脆弱性になる可能性があるので出したくない場合など、例外から来た値を使わずに処理したいという意図を明示できます。

Added support for mixed type

mixed型がサポートされました。

function foo(mixed $arg){}
foo(1);
foo('x');
foo(new stdClass());

// PHP8.0
エラー出ない
// PHP7.4
Uncaught TypeError: Argument 1 passed to foo() must be an instance of mixed, int given

マニュアルでは見慣れた型名ですね。
var_dumpの引数など、あえて多様な型を受け取りたいときに使います。

Added support for Attributes

アトリビュートが追加されました。

#[ExampleAttribute]
function foo(){}

Javaでいうアノテーションです。
これまではPHPDocとかで適当に書いていましたが、これがPHPの構文としてパース可能な形で書けるようになります。

Added support for constructor property promotion

オブジェクト初期化子です。
素直にコンストラクタ引数昇格って言った方がわかりやすい気がしてきた。

class HOGE{
    public function __construct(public int $x){}
}
echo (new HOGE(99))->x;

// PHP8.0
99
// PHP7.4
Parse error:  syntax error, unexpected 'public'

コンストラクタ引数に可視性を書くと、自動的に同名のプロパティを作って値を突っ込んでくれます。
定型文をたくさん書く必要があって面倒だった初期化が楽になります。

コンストラクタ以外には使えません。

Added support for match expression

match式が導入されました。

echo match("1"){
    1 => '1だ',
    true => 'trueだ',
    "1" => '"1"だ',
    default => 'どれでもない',
};

// PHP8.0
"1"
// PHP7.4
Parse error:  syntax error, unexpected '=>'

厳密な比較、フォールスルーしない、返り値を持つ式である、とswitch文の問題の多くを解消した構文です。

Private methods declared on a parent class no longer enforce any inheritance rules

privateメソッドが子クラスに影響しないようになりました。

class A{ 
    private final function foo(int $arg){}
}
class B extends A{ 
    private function foo(string $agg){}
}

// PHP8.0
Warning:  Private methods cannot be final
// PHP7.4
Fatal error:  Cannot override final method A::foo() 

privateメソッドにfinalって書くと継承時に何故かFatal Errorになっていたのですが、これがオーバーライドチェックされないようになります。

かわりにprivateは上書きされないからfinalしなくていいというE_WARNINGが出るようになりました。

Added support for nullsafe operator

ヌル安全オペレータが追加されました。

echo null?->foo();

// PHP8.0
NULL
// PHP7.4
Parse error:  syntax error, unexpected '->'

ヌル安全オペレータ?->は、nullに対してプロパティやメソッドを取り出そうとすると、そこで処理が止まります。

なおnullでなければ止まらないので、(new stdClass)?->foo()とか書くとfoo()が無いってエラーになります。

Added support for named arguments

名前付き引数がサポートされました。

echo htmlspecialchars(encoding:'UTF-8', string:'>');

// PHP8.0
&gt;
// PHP7.4
Parse error:  syntax error, unexpected ':'

引数の多い関数で最後の引数だけ呼んだり、引数の順番を入れ替えたりできます。
また呼び出し側に引数名を書くため、どの引数が何を表しているのか理解しやすくなります。

Date

Added DateTime::createFromInterface() and DateTimeImmutable::createFromInterface()

DateTime::createFromInterfaceDateTimeImmutable::createFromInterfaceが追加されました。

echo (DateTime::createFromInterface(new DateTimeImmutable()))->format('Y-m-d');

// PHP8.0
2020-12-01
// PHP7.4
Fatal error:  Uncaught Error: Call to undefined method DateTime::createFromInterface

名前からするとDateTime::createFromImmutableのように、DateTimeXXXからDateTimeを作るメソッドだと思われます。
現状ではDateTimeとDateTimeImmutableしかないうえにDateTimeInterfaceをユーザがimplementsすることはできないので意味はありませんが、将来種類が増えたときに役立ちそうです。

Added the DateTime format specifier "p"

DateTimeのフォーマットpが追加されました。
Pと同じですが、+00:00のときだけZが返ります。よくわからない。

Dom

Introduce DOMParentNode and DOMChildNode

DOMParentNode、DOMChildNodeが追加されました。
普段SimpleXMLしか使わないからよくわからない。

Enchant

enchant_dict_add / enchant_dict_is_added

enchant_dict_addenchant_dict_is_addedが追加されました。

Enchantを実用で使ってる人っているんだろうか。

FPM

Added a new option pm.status_listen

設定pm.status_listenが追加されました。
異なるエンドポイントのステータスを確認できるとかなんとか。

Hash

HashContext objects can now be serialized

HashContextがシリアライズ可能になりました。

echo serialize(hash_init('md5'));

// PHP8.0
O:11:"HashContext":5
// PHP7.4
Fatal error:  Uncaught Exception: Serialization of 'HashContext' is not allowed

リクエストを超えてハッシュ作成を継続したいとかあるのか?

Opcache

If the opcache.record_warnings ini setting is enabled, opcache will record compile-time warnings

ini設定opcache.record_warningsを有効にした場合、コンパイル時に発生した警告をして次のアクセス時にも発生させます。

OpenSSL

Added Cryptographic Message Syntax (CMS)

Cryptographic Message Syntaxがサポートされました。

Standard

printf() and friends now support the %h and %H format specifiers

printfおよび類似関数が、指定子%h%Hをサポートしました。
%g%Gと同じですが、ロケールに関わらず小数点が.になります。

setlocale(LC_ALL, 'fr');
echo sprintf('%g - %h', 1.2, 3.4);

// PHP8.0
1,2 -
// PHP7.4
1,2 - 3.4

上の方にもあったフランス語問題の対応です。

printf() and friends now support using "*" as width or precision

printfおよび類似関数が、量指定子*をサポートしました。

printf("%.*f", 10, 1.2);

// PHP8.0
1.2000000000
// PHP7.4
f

精度を外側から渡すことができます。
これまでも文字列結合などで実現は可能でしたが、簡単に書けるようになります。

proc_open() now supports pseudo-terminal (PTY) descriptors

proc_openが疑似ターミナルをサポートしました。

$proc = proc_open('dir', [['pty'], ['pty'], ['pty']], $pipes);

// PHP8.0
Warning:  proc_open(): PTY (pseudoterminal) not supported on this system
// PHP7.4
Warning:  proc_open(): pty pseudo terminal not supported on this system

なんにしろWindowsではサポートされてなかった。

proc_open() now supports socket pair descriptors

proc_openがソケットをサポートしました。

$proc = proc_open('dir', [['socket'], ['socket'], ['socket']], $pipes);

// PHP8.0
エラー出ない
// PHP7.4
Warning:  proc_open(): socket is not a valid descriptor spec/mode

こちらはWindowsでも使えます。

Sorting functions are now stable

ソート関数が安定ソートになりました。

$array = [
    'a' => 1,
    'b' => 1,
];
asort($array);

これまではソート後にaとbの位置がどうなっているか保証がなかったのですが、今後はaとbの順番が保たれます。

array_diff(), array_intersect() and their variations can now be used with a single array as argument

array_diffなどを引数ひとつで使えるようになりました。

array_diff([1, 2]);

// PHP8.0
エラー出ない
// PHP7.4
Warning:  array_diff(): at least 2 parameters are required, 1 given

単独だと意味はないですが、array_intersect(...$arrays)のように展開して渡したいときに役立ちます。

The $flag parameter of ob_implicit_flush() was changed to accept bool values

ob_implicit_flushの引数$flagがbool型になりました。

Zip

Extension updated to version 1.19.1

zipエクステンションのバージョンが1.19.1になりました。
と言われてもこれがlibzipのことかすらよくわからないのですが。

New ZipArchive::lastId property

ZipArchiveにlastIdプロパティが追加されました。
最後に追加したファイルのインデックスです。

Error can be checked after an archive is closed

ZipArchiveを閉じた後でも、ZipArchive::statusなどでステータスを確認可能になりました。

The remove_path option of ZipArchive::addGlob() and ::addPattern() is now treated as arbitrary string prefix

ZipArchive::addGlobおよびZipArchive::addPatternのオプションremove_pathは、アーカイブを追加する際に削除される接頭辞になりました。
以前はディレクトリ名でしたが、add_pathと整合が取れていなかったので変更されました。

Optional compression / encryption features are listed in phpinfo

圧縮/暗号化オプションがphpinfoに表示されるようになりました。

Changes in SAPI modules

SAPIモジュールへの変更

Apache

The PHP module has been renamed

Apache PHPモジュールの名前がphp7_moduleからphp_moduleに変更されました。

今後メジャーバージョンアップのたびにLoadModule php8_moduleとか書き替えずに済むようになります。

Deprecated Functionality

非推奨になる機能。
ここで『できなくなった』と書いてあるのは非推奨と読み替えてください。

Core

Declaring a required parameter after an optional one is deprecated

省略可能引数の後で必須引数を記述することができなくなりました。

function hoge($foo = 'foo', $bar){}

// PHP8.0
Deprecated:  Required parameter $bar follows optional parameter $foo
// PHP7.4
エラー出ない

今まで許可されていたことにびっくりですね。

Calling get_defined_functions() with $exclude_disabled explicitly set to false is deprecated

get_defined_functionsの引数$exclude_disabledにfalse指定ができなくなりました。

互換性のない変更点において、無効化された関数は存在しないものにされました。
そのため、このフラグには意味がなくなりました。

Enchant

enchant_broker_set_dict_path and enchant_broker_get_dict_path not available

libenchant-2では、enchant_broker_set_dict_pathenchant_broker_get_dict_pathが使えなくなりました。

enchant_dict_add_to_personal, use enchant_dict_add instead

enchant_dict_add_to_personalが非推奨になりました。
かわりにenchant_dict_addを使います。

enchant_dict_is_in_session, use enchant_dict_is_added instead

enchant_dict_is_in_sessionが非推奨になりました。
かわりにenchant_dict_is_addedを使います。

enchant_broker_free and enchant_broker_free_dict, unset the object instead

enchant_broker_freeenchant_broker_free_dictが非推奨になりました。
かわりにunsetを使います。

ENCHANT_MYSPELL and ENCHANT_ISPELL constants

定数が非推奨になりました。

LibXML

libxml_disable_entity_loader() has been deprecated

libxml_disable_entity_loaderが非推奨になりました。
XXE脆弱性対応のためです。

PGSQL / PDO PGSQL

The constant PG_VERSION_STR has now the same value as PG_VERSION, and thus is deprecated

定数PG_VERSION_STRは中身がPG_VERSIONと同じになり、さらに非推奨になりました。

PG_VERSION_STRもPG_VERSIONもPostgresの項目ににないんだけど何処で定義されてるんだろう。

Function aliases in the pgsql extension have been deprecated

Postgres関数の古い呼び名のエイリアスが非推奨になりました。

たとえばpg_lo_openにはpg_loopenというエイリアスが存在し、PHP7.4までは警告なく使用可能でした。

Zip

Using empty file as ZipArchive is deprecated

空のファイルをZipArchiveとして扱うことができなくなりました。
libzip 1.6.0で非対応になったためです。

The procedural API of Zip is deprecated

手続き型のzip関数は非推奨になりました。

zip_open('tmp');

// PHP8.0
Deprecated:  Function zip_open() is deprecated
// PHP7.4
エラー出ない

ZipArchiveを使いましょう。

Reflection

ReflectionFunction::isDisabled() is deprecated

ReflectionFunction::isDisabledが非推奨になりました。
無効化された関数は存在しないものにされたためです。

ReflectionParameter::getClass(), ReflectionParameter::isArray(), and ReflectionParameter::isCallable() are deprecated

ReflectionParameter::getClassReflectionParameter::isArrayReflectionParameter::isCallableが非推奨になりました。

かわりにReflectionParameter::getTypeReflectionTypeを使いましょう。

Changed Functions

仕様が変更になる関数。

Reflection

ReflectionClass::getConstants and ReflectionClass::getReflectionConstants results can be now filtered

https://www.php.net/manual/ja/reflectionclass.getconstantsReflectionClass::getReflectionConstantsに引数$filterが追加されました。
またフィルタ用の定数ReflectionClassConstant::[IS_PUBLIC|IS_PROTECTED|IS_PRIVATE]が追加されました。

class FOO{
    public const A = 'A';
    protected const B = 'B';
    private const C = 'C';
}
$c = (new ReflectionClass('FOO'))->getReflectionConstants(filter:ReflectionClassConstant::IS_PRIVATE);
// privateの定数Cだけ出てくる

引数未指定のときはこれまでどおり全て出てきます。

Zip

ZipArchive::addGlob and ZipArchive::addPattern methods accept more values in the "options"

ZipArchive::addGlobZipArchive::addPatternの引数$optionsに対象が追加されました。

  • flags
  • comp_method
  • comp_flags
  • env_method
  • enc_password

ZipArchive::addEmptyDir, ZipArchive::addFile and aZipArchive::addFromString methods have a new "flags" argument

ZipArchive::addEmptyDirZipArchive::addFileZipArchive::addFromStringに引数$flagsが追加されました。

ZipArchive::extractTo now restore file modification time

ZipArchive::extractToがmtimeを復元するようになりました。
これまでは解凍した時刻になっていました。

New Functions

新しい関数。

Core

Added get_resource_id($resource) function

関数get_resource_idが追加されました。

echo get_resource_id(tmpfile()); // 4 環境によって変わる

リソースのリソースIDを取得できます。
(int)$resourceと同じですが、関数的に記述することができます。

ldap_count_references

関数ldap_count_referencesが追加されました。

よくわかりませんが、おそらくWindowsAPIなどと同じものでしょう。
他のLDAP関数ほぼ共通ですし。

OpenSSL

Added openssl_cms_encrypt() / openssl_cms_decrypt() / openssl_cms_read() / openssl_cms_sign() / openssl_cms_verify()

CMS関連の関数が追加されました。
Cryptographic Message Syntaxサポートに伴い導入されたものです。

openssl_cms_encrypt('input.txt', 'output.txt', file_get_contents('key.pem'), []);

使い方はopenssl_pkcs7_XXXXXと相似です。

PCRE

Added preg_last_error_msg

preg_last_error_msgが追加されました。

preg_match('/(?:\D+|<\d+>)*[!?]/', 'foobar foobar foobar');
echo preg_last_error_msg(); // Backtrack limit exhausted

正規表現で最後に発生したエラーメッセージを返します。
意味的にはpreg_last_errorと同じですが、こちらはintでわかりにくいため、メッセージとして出力するようにしたものです。

SQLite3

Add SQLite3::setAuthorizer() and respective class constants

SQLite3::setAuthorizerが追加されました。

SQLを実行するたびに呼ばれるコールバックを設定することができます。

Standard

str_contains / str_starts_with / str_ends_with

str_containsstr_starts_withstr_ends_withが追加されました。

str_contains('森久保乃々', '森久保'); // true
str_starts_with('望月聖', '望月'); // true
str_ends_with('イヴ・サンタクロース', 'サンタクロース'); // true

それぞれ○○が含まれる○○で始まる/終わる、の文字列検索を楽にする関数です。

Added fdiv() function

fdivが追加されました。

echo fdiv(1, 0); // INF

IEEE 754に準拠した割り算です。

Added get_debug_type() function

get_debug_typeが追加されました。

echo get_debug_type(1), get_debug_type(new DateTime()); // int, DateTime

プリミティブ型の型名を返すgettypeと、オブジェクトのクラス名を返すget_class悪魔合体です。

Zip

ZipArchive::setMtimeName and ZipArchive::setMtimeIndex

ZipArchive::setMtimeNameZipArchive::setMtimeIndexが追加されました。
mtimeを操作できます。

ZipArchive::setProgressCallback / ZipArchive::setCancelCallback

ZipArchive::setProgressCallbackとZipArchive::setCancelCallbackが追加されました。

ってUPGRADINGには書いてあるんだけど、試してみるとCall to undefinedって言われるしregisterProgressCallbackregisterCancelCallbackが存在するのでおそらく間違い。

ZipArchive::replaceFile

ZipArchive::replaceFileが追加されました。
中身のファイルの一部を置き換えるみたいだけど、そんなややこしいことをPHPで行うことはあるのだろうか。

ZipArchive::isCompressionMethodSupported / ZipArchive::isEncryptionMethodSupported

ZipArchive::isCompressionMethodSupportedZipArchive::isEncryptionMethodSupportedが追加されました。

名前のとおり、圧縮方式および暗号化方式をサポートしているか確認できます。

New Classes and Interfaces

新しいクラス、インターフェイス。

Tokenizer

PhpToken

PhpTokenクラスが追加されました。

echo PhpToken::tokenize('<?php echo; ?>')[0]->text; // <?php

既存のTokenizerをオブジェクト的に扱えるようにしたものです。

Removed Extensions and SAPIs

削除されたエクステンション、SAPIなど。

XML-RPC

The xmlrpc extension has been unbundled and moved to PECL

XML-RPCがPHPコアから削除され、PECL行きになりました。
内部で使っているxmlrpc-epi開発放棄されているためです。

Other Changes to Extensions

そのほかの変更点。

CURL

The CURL extension now requires at least libcurl 7.29.0

Curlエクステンションがlibcurlの7.29.0以上を必要とするようになりました。

curl_init() will now return a CurlHandle object rather than a resource

curl_initはリソースではなくCurlHandleオブジェクトを返すようになりました。

var_dump(
    curl_init()
);

// PHP8.0
object(CurlHandle)#1
// PHP7.4
resource(4) of type (curl)

他のCurl関数はこれまでと同じ書き方で透過的に動作します。
curl_closeは実質的に何もしなくなりました。

curl_multi_init() will now return a CurlMultiHandle object rather than a resource

curl_multi_initはリソースではなくCurlMultiHandleオブジェクトを返すようになりました。

それ以外はcurl_initとほぼ同じです。

curl_share_init() will now return a CurlShareHandle object rather than a resource

curl_share_initはリソースではなくCurlShareHandleオブジェクトを返すようになりました。

それ以外はcurl_initとほぼ同じです。

The deprecated parameter $version of curl_version() has been removed

非推奨だったcurl_versionの第一引数$versionが削除されました。

マニュアルからは既に抹消されています。

Date

DatePeriod now implements IteratorAggregate

DatePeriodIteratorAggregateをimplementsするようになりました。

(new ReflectionClass('DatePeriod'))->getInterfaceNames(); // [IteratorAggregate, Traversable]

以前はTraversableでした。

DOM

DOMNamedNodeMap / DOMNodeList now implements IteratorAggregate

DOMNamedNodeMapおよびDOMNodeListIteratorAggregateをimplementsするようになりました。
これらも以前はTraversableでした。

Intl

IntlBreakIterator / ResourceBundle now implements IteratorAggregate

IntlBreakIteratorおよびResourceBundleIteratorAggregateをimplementsするようになりました。
以前はTraversableでした。

Enchant

The enchant extension now uses libenchant-2 by default when available

Enchantエクステンションが使用しているlibenchantのバージョンがlibenchant-2になりました。
1系は非推奨となり、将来的に削除されます。

GD

The $num_points parameter of imagepolygon(), imageopenpolygon() and imagefilledpolygon() is now optional

imagepolygonimageopenpolygonimagefilledpolygonの引数$num_pointsが省略可能になりました。
省略した場合、$points / 2とみなされます。

imagepolygon( $im=imagecreatetruecolor(100, 100), [1,2,3,4,5,6], imagecolorallocate($im, 255,255,255))

第四引数$colorは省略不可のため、省略する場合は第三引数に$colorを入れる、すなわち引数の数によって第三引数の意味が変わるという非常にわかりにくいことになります。
負債を増やしてどうする。

imagepolygon( $im=imagecreatetruecolor(100, 100), [1,2,3,4,5,6], null, imagecolorallocate($im, 255,255,255));
// Fatal error:  Uncaught ValueError: imagepolygon(): Argument #3 ($num_points_or_color) must be greater than or equal to 3

imagepolygon( $im=imagecreatetruecolor(100, 100), points:[1,2,3,4,5,6], color:imagecolorallocate($im, 255,255,255))
// Fatal error:  Uncaught ArgumentCountError: imagepolygon(): Argument #3 ($num_points_or_color) not passed

nullにしたり名前付き引数で飛ばしたりといったこともできません。
これはもっとどうにかしたほうがよかったのでは。

The function imagegetinterpolation() to get the current interpolation method has been added

imagegetinterpolationが追加されました。

$im=imagecreatetruecolor(100, 100);
imagesetinterpolation($im, IMG_GENERALIZED_CUBIC);
echo imagegetinterpolation($im); // 11

現在設定されている補間方法を取得します。

JSON

The JSON extension cannot be disabled anymore

JSONエクステンションを無効にすることができなくなりました。

マニュアルには全く載っていないのですが、実はこれまでコンパイルオプション--disable-jsonで無効化することが可能でした。

MBString

The Unicode data tables have been updated to version 13.0.0

Unicodeデータベースが13.0.0にバージョンアップされました。

PDO

PDOStatement now implements IteratorAggregate

PDOStatementIteratorAggregateをimplementsするようになりました。
以前はTraversableでした。

LibXML

The minimum required libxml version is now 2.9.0

要求するlibxmlがバージョン2.9.0以上になりました。

以前は2.6.0以上でした。

MySQLi / PDO MySQL

When mysqlnd is not used the minimum supported libmysqlclient version is now 5.5

要求するlibmysqlclientのバージョンが5.5以上になりました。
libmysqlclientではなくmysqlndを使う場合は不要です。

ところでマニュアルには『mysqlndを勧める』って書いてあるんだけど、変更履歴では『libmysqlclientが推奨だしデフォルトもこっち』って書いてあるんですよね。
どっちが正解なんだ。

mysqli_result now implements IteratorAggregate

mysqli_resultIteratorAggregateをimplementsするようになりました。
以前はTraversableでした。

PGSQL / PDO PGSQL

The PGSQL and PDO PGSQL extensions now require at least libpq 9.1

要求するlibpqのバージョンが9.1以上になりました。

Readline

Calling readline_completion_function() before the interactive prompt starts will now override the default interactive prompt completion function

readline_completion_functionは、対話型シェルが起動していないときにも動くようになりました。
以前は、既に対話型シェルが起動していないと動きませんでした。

SimpleXML

SimpleXMLElement now implements RecursiveIterator and absorbed the functionality of SimpleXMLIterator

SimpleXMLElementRecursiveIteratorをimplementsするようになりました。
以前はTraversableでした。

さらにSimpleXMLIteratorの機能を吸収しました。
SimpleXMLIteratorは実質的にSimpleXMLElementと同じになりました。

Shmop

shmop_open() will now return a Shmop object rather than a resource

shmop_openはリソースではなくShmopオブジェクトを返すようになりました。

他の共有メモリ関数はこれまでと同じ書き方で透過的に動作します。
shmop_closeは実質的に何もしなくなります。

New Global Constants

新たなグローバル定数。

Filter

FILTER_VALIDATE_BOOL has been added as an alias for FILTER_VALIDATE_BOOLEAN

検証フィルタFILTER_VALIDATE_BOOLが追加されました。

var_dump(FILTER_VALIDATE_BOOL, FILTER_VALIDATE_BOOLEAN); // 258, 258

これはFILTER_VALIDATE_BOOLEANのエイリアスです。
真偽型の表記はboolに揃えられつつあるので、他のbooleanもいずれboolに変更されるでしょう。

Changes to INI File Handling

iniファイルの変更。

zend.exception_string_param_max_len

ini設定zend.exception_string_param_max_len追加されました
スタックトレースに表示される引数の文字数を制御します。

try {
    substr('12345678901234567890');
} catch(Throwable $e) {
    echo $e->getTraceAsString();
}

// zend.exception_string_param_max_len=15のとき
#0 C:\xampp\htdocs\test.php(10): substr('123456789012345...')

// zend.exception_string_param_max_len=1のとき
#0 C:\xampp\htdocs\test.php(6): substr('1...')

開発時のデバッグなどに重宝するでしょう。

デフォルトは15で、範囲は0から1000000です。
ini_setで変更できたのでPHP_INI_ALLのようです。

com.dotnet_version

dotnetを使う際の.netフレームワークのバージョンを指定できるようです。

Windows Support

Standard

Program execution functions using the shell now consistently execute %comspec% /s /c "$commandline",

execなどのシェルコマンド実行関数が、一貫して%comspec% /s /c "$commandline"という形で実行されるようになりました。

ということらしいですがよくわかりません。

GD

php_gd2.dll has been renamed to php_gd.dll

GDエクステンションのファイル名がphp_gd2.dllからphp_gd.dllに変更されました。

php-test-pack

The test runner has been renamed from run-test.php to run-tests.php

テストパッケージのテストランナーファイル名がrun-test.phpからrun-tests.phpに変更されました。

Other Changes

その他の変更点

EBCDIC targets are no longer supported

文字コードEBCDICがサポート対象外になりました。

果たして使っていた人はいるのだろうか?

Performance Improvements

パフォーマンスについて。

A Just-In-Time (JIT) compiler has been added to the opcache extension

JITがサポートされました。

function getFibonacci(int $i){
    return $i < 2 ? $i : getFibonacci($i-1) + getFibonacci($i-2);
}

$a = microtime(true);
echo getFibonacci(38);
$b = microtime(true);
echo $b - $a;
PHP8デフォルト opcache有効 JIT有効
6.931639 5.833915 1.698887
7.443145 5.536612 2.009317
6.610769 5.619338 1.662739
6.794568 5.654112 1.739347
7.286194 5.774223 1.788053

CPUバウンドな処理はとんでもない速さになります。

ただPHPの主な用途はディスクやデータベースへのIOで、それらにはJITの恩恵はほぼないので、実際のWebアプリケーションはここまで極端に速くはなりません。
公式LPではざっくり1割から3割減というところですね。
02.png

感想

変更点多すぎ!!!!
PHP8たいしたことないんじゃねえのとか言ってた奴土下座な。

互換性のない変更の多くは、古くからのアバウトな仕様を排除し、厳密な書き方を推奨する方向に向けるものです。
implodeの引数が逆順でも書けるなんてのはその最たるものですね。
そのぶん昔からの熟成されたソースを使っているところは移行が大変になると思いますが、PHPは互換を切ってでも進化し続ける方向を選んだということなので、まあがんばってもらいましょう。
それにPHP7以降の近代風な書き方をしている限りでは、変更が必要なところはほとんどありません。

そして新機能については、流行りの書式やら文法などを貪欲に取り入れていて、このあたりはさすがPHPってところです。
節操がないとも言えますが、その節操のなさこそがPHPたる所以と言えましょう。
アロー関数や型宣言、アトリビュートあたりはまだ機能が半端なので、次のメジャーバージョンかあるいはマイナーバージョンでさらに進化しそうですね。

さらにJITが入ったことで、Webアプリケーションだけに囚われない、実用的な計算ライブラリでもPHPが輝く時代がやってくるかもしれません。
まあC++やGoあたりとはさすがに比べものにはならないですが、他のスクリプト言語に比べればだいぶいい線行っているのではないでしょうか。

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