- 投稿日:2020-12-01T22:50:57+09:00
異業種から転職して感じたwebエンジニアに必要な素養
とりあえず箇条書き
・分かりづらいことを分かりやすく説明する能力
・急な内容修正などに対する対応力と柔軟性
・チームの人に対する思いやりと気遣い(コードの工夫やコミュニケーションなどで)
・自分の書くコードに過度な愛着やこだわりを持たない
・自分の思い通りにいかなくても許容する広い心
・ファイル内容やDB構造など広く記憶する記憶力
・どんなバグにもへこたれない強いメンタル
・行き詰ったらすぐに質問をする
・手遅れになる前に相談する
・小まめに進捗報告をする(実は気付いていない内容がよくある)
・どんな状況においてもポジティブシンキング
・自分の間違いを認められること
・相手の間違いを許せること
・意見が合わなくてもレスバしないでうまく妥協点を見つける、その場を丸く収める
・納期を守る、約束を守る
・ダブルチェック、確認をしっかりとする
・あいさつをする
・遅刻しない
・整理整頓
・清潔感
・笑顔もうエンジニア関係なくなっちゃった!
今日の名言
If today were the last day of my life, would I want to do what I am about to do today?
(もし今日が人生最後の日だとしたら、今しようとしていることが、本当にしたいことだろうか?)-スティーブ・ジョブズ
- 投稿日:2020-12-01T21:42:36+09:00
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-dockerdockerで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.phpPHP8新機能
PHP8の新機能はここから抜粋しています。
PHP: PHP 8.0.0 Release AnnouncementNamed 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"; // <a href='test'>Test</a> ?>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); // ngMatch 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 AnnouncementDockerを利用すれば、PHP8に限らず様々な環境を楽に構築できるのでこれからもどんどん利用していきたいと思います。
参考
- 投稿日:2020-12-01T21:02:07+09:00
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-generatorcomposer 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.phpWay\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"で実行しないテーブルの指定ができるみたいです。
互換性の問題ってわかってるならあれこれやる前にバージョン下げてしまえばよいだけの話でした
- 投稿日:2020-12-01T19:30:49+09:00
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 RethansLaravel
Laravel Framework 6.18.35composer.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" }変更していく
下記のドキュメントを参考にしながら行っていく
- 6.xから7.0へのアップグレード
- 7.xから8.0へのアップグレード
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 255Symfony5へのアップグレード
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.1Next step
PJというか環境によっては上記までの対応では不足しているので動作確認をしながら、エラーや挙動がおかしい箇所に関してアップグレードガイドを参考にしていく
特に「影響の可能性: 高い」の部分は要チェック
- 6.xから7.0へのアップグレード
- 7.xから8.0へのアップグレード
参考までに、「影響の可能性: 低い」とされていても 自分のPJでは
$model->getOriginal
を利用していたので対応が必要になったり、 app/Providers/RouteServiceProvider.php をごにょごにょしていたので変更が必要だったりライブラリを更新した影響でLaravelと関係ないところで更新を迫られたりとまぁちょいちょいありました
アップグレードガイド見るよりコード差分見て行くほうが早いかもしれない
https://github.com/laravel/laravel/compare/6.x...8.x
- 投稿日:2020-12-01T18:51:28+09:00
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#transactionsiOS
"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)を検証することで、レシートが改ざんされていないかチェックできます。
リクエストで送られてきたreceipt
とsignature
をGoogle Play Consoleから取得できるRSA公開鍵で検証します。公開鍵のBase64文字列はGoogle Play Consoleから取得します。
取得した文字列を
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.phpRoute::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; } }.envBUNDLE_ID=com.example.billingtest PRODUCT_IDS=coins100,coins200 PUBKEY="-----BEGIN PUBLIC KEY----- **************************************************************** **************************************************************** **************************************************************** **************************************************************** **************************************************************** **************************************************************** ******** -----END PUBLIC KEY-----"仕様通りできていればcordova-plugin-purchaseから呼び出せるはずです。
- 投稿日:2020-12-01T18:25:18+09:00
YOUは何処から日本へ?(ページにアクセスしたユーザーがどこから来たか調べる)
アクセスしたユーザーの国や地域、IPアドレス取得
案件で多言語サイトのデフォルト言語をユーザーごとに出し分けたいという要望があったため
フロント、サーバー側で国や地域、IPアドレスを取得する方法を調査したJavaScripts で対応
JS単体では端末のIPを取得することはできないがAPIを利用することで取得できる
以下は中でもフリーのサービスを抜粋したもの1. ipinfo.io
アクセスするとIPを取得して表示してくれるサービス。
JSON形式で情報が返却される。
アカウント作成とアクセス回数に上限がある
ipinfo.io2. Geolocation API
WiFi、GPS、IPアドレスなどから位置情報を取得するAPI。
JSON形式で情報が返却される。
位置情報の取得にはユーザーの許可が必要なため拒否されれば当然取得できない。
WiFiや緯度経度からの取得はGoogleが提供していることが多いため中国などでは利用できないことがある。
Geolocation API3. ipify
フリーのIP取得サービス。
JSON形式で情報が返却出来る。
上限は無し。
ipify
API4. jsonip.com
無料無制限のIP取得サービス。
JSON形式でIPを取得できるがおまけに広告とドキュメントのURLが含まれている。
以前は政治的メッセージも含まれていたがなくなったらしい。
上限なし。
jsonip.com
API5. JSON Test
フリーのIP等取得サービス。
IPアドレス単体やリクエストヘッダー情報をJSON形式で取得できる。
名前のせいで情報が見つけにくい。
JSON Test
API6. ipapi.co
フリーと商用ライセンスのあるIP等取得サービス。
アカウント登録が必要。
IPだけでなく国や地域、緯度経度など詳細なデータをJSON形式で取得できる。
珍しくドキュメントもそろっているので親切。
ipapi.coPHP で対応
PHPはIPアドレスを取得できるが国や地域を取得するにはライブラリかAPIが必要になる(一部有料)
1. IPinfoAPI
IPアドレスを渡すと国や地域のコードをJSON形式で取得できるサービス
上限が毎秒2回まで。2. Geo IP2
MaxMind社から提供されたデータベースファイルをサーバー側に設置しアクセスしたIPで検索してどの国や地域からアクセスがあったかを調べる。
無償版ライセンスでは国まで、有償版では地域まで取得できるので用途によって使い分け可能。
Geo IPAWS で対応
AWSのCloudFront-Viewer-Country はIPアドレスから判断したアクセス元の国コードを返却するためAWSを利用していれば対応可能。
AWSまとめ
JavaScriptsは単体ではIPを取得できないがAPIを使用すればIPもしくは国コードなどのアクセス元情報が取得できる。
ただしIPアドレスの取得方法によっては対象外の国や地域があるため注意が必要。PHPはIPアドレスは単体で取得可能だがアクセス元の国や地域のコードを取得するにはライブラリ、APIが必要。
ライブラリのライセンスは詳細な情報が必要であれば有償版、国コードが必要なだけであれば無償版でよい。その案件がAWSを利用しているのであればCloudFront-Viewer-Country で対応可能
- 投稿日:2020-12-01T17:27:17+09:00
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' );同じように新着情報や、スタッフ情報などのカスタム投稿を追加することができます。複数の投稿を追加するにはラベルを追加します。
カスタム投稿のアイコンの変更
管理画面に表示させるアイコンは、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 = ”;」の部分に変更したい文字を入れます。
これで「タイトル名」から「イベント名」に変更となりました。カスタム投稿に入力エリアを追加
次に先ほど追加したカスタム投稿に任意の入力エリアを追加します。
例としてイベント情報にイベント期間を入力する欄を設けます。「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
これでカスタム投稿ページにイベント期間を入力する項目が追加されました。カスタム投稿に入力内容を保存
入力するエリアは追加されましたが、入力した値を保存しないといけないのでそのコードを記述します。
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' );これでカスタム投稿ページにイベント情報・期間の入力エリアが表示され、入力された値を使用することができます。
カスタム投稿一覧ページのレイアウトを変更
カスタム投稿ページで新規にページを追加します。
すると一覧ページは下記のような表示になると思います
せっかくなので先ほど追加した、イベント期間とアイキャッチをイベント情報一覧ページで表示させます。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 );
これで入力したイベント期間と設定したアイキャッチが一覧ページに表示されました。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 フィルターフック から外してやる。
- 投稿日:2020-12-01T16:22:52+09:00
[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以上!
- 投稿日:2020-12-01T15:35:05+09:00
きっと正しくないレンタルサーバーの作り方 Vol.2 - ざっくりアーキテクト
はじめに
前回の記事からだいぶ時間が経ちましたが、第2回めです(・∀・)。
今回は全体的なアーキテクトについて記述していこうと思います(・∀・)。※注意!
この一連の記事で紹介するコードは動作の概念を説明するものでありセキュリティーなどは意識していません(・∀・)。実際に運用するシステムなどに使用しないでください(・∀・)。
(そのまま使うひともいないと思いますが)また、私も記事を書きながら開発をしていくので「後になってみたら最初の方の記事間違えてたー」なんて事は起きそうです(・∀・)。
ご了承ください(・∀・)。
目次
全体の構成
基本的に単一のサーバーで動作しますが、後から複数台構成に移行しやすいような構成を目指します(・∀・)。
アカウント管理
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」を取得した場合、
- taro@mao.rentaserve.com
- /home/mao/public_html/mao.rentaserve.com 以下にアクセス可能
- jiro@example.com
- /home/mao/public_html/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 のワイルドカードドメインを取得するためちょっと細工が必要そうです(・∀・)。
最後に
つらつらと長くなりましたが、次回から実際にサーバーを構築していこうと思います(・∀・)。
- 投稿日:2020-12-01T14:57:30+09:00
【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.phppublic static function all($columns = ['*']) { return static::query()->get( is_array($columns) ? $columns : func_get_args() ); }
- 投稿日:2020-12-01T14:05:48+09:00
[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今回は
SEMVER WARNING: Recommended action is a potentially breaking change
というエラーが2つ出てきました。ここでターミナルでnpm audit fix
を実行しましょう。//ターミナルにて npm audit fixしかし、下記の画像のようにまだエラーを修復できませんでした。
これでも突破できる方法がnpm audit fix --force
というものがあります。
--forceはオプションになり強制的に修復させるというやり方です。//ターミナルにて npm audit fix --force
- 投稿日:2020-12-01T12:35:22+09:00
英語版リーダブルコード(The Art of Readable Code)を読んで日本語訳で要点を殴り書きしてみた。
【最初に】
英語版リーダブルコード(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
- 投稿日:2020-12-01T12:35:12+09:00
Laravel 8にてRouteの記述につまずいた話
- 投稿日:2020-12-01T11:04:07+09:00
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アプリを作りたく、その部品づくりの一環として、今回のチェックを数える試作品を作っておりました。
ポートフォリオ完成に近づいたら、もっと無駄のないコードができていると思うので、また再投稿いたします!
- 投稿日:2020-12-01T09:21:29+09:00
Web エンジニアが始める Server-side Swift "Vapor"
はじめに
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 公式
- Vapor GitHub
Vapor は今のところ Mac と Linux で利用可能です。Mac だと Homebrew、Linux では yum や apt などでパッケージが提供されています。あくまでも実行環境ということなので docker や Homestead を使えば Windows でも開発は可能です。
余談ですが、Swift 製の Web アプリケーションフレームワークと言えば当初は IBM が開発する Kitura が有名でしたが、調べたら2020年1月に(IBMとして)開発を終了しているようでした。
- Kitura 公式
- Kitura GitHub
サーバサイドを 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: MongoFluent を使用すると言うことはその後ろにデータベースがあるわけですので、次でデータベース製品を選択します。PostgreSQL が Recomended なので、1 を入力。
Would you like to use Leaf? (--leaf/--no-leaf) y/n>次は Leaf の追加。こちらは Laravel で言うところの Blade テンプレートシステムです。もちろん y を入力。
すると完了メッセージと共に下記のようなアスキーアートが表示されます。
アスキーアートと言えば、昔アドベントカレンダーで書いた自分の記事を思い出しました。全然関係ないけど。
- 【アスキーアート職人歓喜?】画像からアスキーアートを生成
Xcode の起動
プロジェクトディレクトリに移動して下記コマンドで Xcode が起動します。
$ cd cd ApiTest $ vapor xcode初回起動時は依存パッケージがダウンロードされるのでしばらく待って、左上のビルドボタン(再生ボタン)がアクティブになるまで待ってください。
依存バッケージのダウンロードが完了したら、ビルドを実行してみましょう。ショートカットキー「⌘ + 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 したような感じですね。ではブラウザでアクセスしましょう。
軽く構成を眺める
初期構成のファイル構成を軽く見てみましょう。
既視感があると言うかなんと言うか、もうフォルダ名のファイル名で大体わかっちゃいますよね。主要なファイルをさくっと紹介。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 は今年もアドベントカレンダーに参加しています!
応援よろしくお願いします!
qnote Advent Calendar 2020
過去の猫会社アドベントカレンダー
- 投稿日:2020-12-01T01:57:40+09:00
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-php4. 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);
- 投稿日:2020-12-01T00:37:56+09:00
一体いつから PHPがセミコロン省略できないと錯覚していた?
・ヮ・)あ、おはようございまーす
PHPでセミコロン書いてますか?PHPはCで実装されていてJavaに強く影響を受けているためか、行末にセミコロンを付けないといけません。
Python、Go、Swift、kotlin、Ruby、JavaScript などセミコロンを書かない(または省略してもいい)言語を使っている方からは
しばしば白い目で見られがちです(諸説あり)正直、悔しいです…
PHPの真の力(笑) を見せつけます!
【朗報】PHPにはセミコロンを省略できる構文が2つある
if
1つ目は
if 文
です
PHPのif文
は、戻り値があるようなものであれば 変数だろうが、関数であろうが、 式でもいれることができます$var = 0; if ($var) {} // 変数はもちろんOK if (fn() => 0) {} // クロージャー、アロー関数ももちろんOK if ($var = 1) {} // 代入式でもOKしかし 戻り値が無いような式は入れることができません
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のコードの中にも埋め込むことができます
<?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は黙ってセミコロン書いたほうがいいですね
なんでこんな物を書こうと思ったのか不明です
- 投稿日:2020-12-01T00:21:00+09:00
[PHP]標準入力で与えられる値を取得する
- 投稿日:2020-12-01T00:20:06+09:00
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.参考
- https://laravel.com/api/8.x/Illuminate/Database/Schema/Builder.html#method_enableForeignKeyConstraints
- https://laravel.com/api/8.x/Illuminate/Database/MySqlConnection.html
- https://github.com/doctrine/dbal/blob/3.0.x/src/Schema/MySQLSchemaManager.php
- https://github.com/doctrine/dbal/blob/3.0.x/src/Schema/AbstractSchemaManager.php#L213-L228
補足: 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(); } }
- 投稿日:2020-12-01T00:20:06+09:00
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.参考
- https://laravel.com/api/8.x/Illuminate/Database/Schema/Builder.html#method_enableForeignKeyConstraints
- https://laravel.com/api/8.x/Illuminate/Database/MySqlConnection.html
- https://github.com/doctrine/dbal/blob/3.0.x/src/Schema/MySQLSchemaManager.php
- https://github.com/doctrine/dbal/blob/3.0.x/src/Schema/AbstractSchemaManager.php#L213-L228
補足: 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秒
- 投稿日:2020-12-01T00:20:06+09:00
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.参考
- https://laravel.com/api/8.x/Illuminate/Database/Schema/Builder.html#method_enableForeignKeyConstraints
- https://laravel.com/api/8.x/Illuminate/Database/MySqlConnection.html
- https://github.com/doctrine/dbal/blob/3.0.x/src/Schema/MySQLSchemaManager.php
- https://github.com/doctrine/dbal/blob/3.0.x/src/Schema/AbstractSchemaManager.php#L213-L228
補足: 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秒
- 投稿日:2020-12-01T00:01:27+09:00
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) failedPHP7.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 deprecatedtrueにすると定数が大文字小文字を区別しなくなるという意味のわからない機能です。
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 deprecatedproperty_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 parentPHP7.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 ExceptionCompile 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 referencePHP7.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 referencePHP7のころから、値を直接渡した場合は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.ini
にdisable_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 reasonsPHP7.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 deprecatedPHPの三項演算子は他の大半の言語と挙動が異なるという問題がありましたが、それを合わせるための布石です。
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 encounteredPHP7.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
mktimeとgmmktimeは、最低ひとつの引数が必要になりました。
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_dictとenchant_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_REQUIRED
とFILTER_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
同じく非推奨だったpng2wbmpとjpeg2wbmpも削除されました。
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 deprecatedIMG_CROP_DEFAULTなどの定数を使いましょう。
GMP
gmp_random() has been removed
gmp_randomが削除されました。
この関数はプラットフォームに依存して出力が変わるという問題があったため、PHP7.2でE_DEPRECATEDになりました。
かわりにgmp_random_bitsかgmp_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_sort、ldap_control_paged_result、ldap_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_MAIL、mb_get_infoの
func_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_eregやmb_regex_encodingなどには、
mbereg
やmbregex_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_eregとmb_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_readとopenssl_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_sealとopenssl_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ではそれがなくなっていました。
これ使ってる人とかいるんだろうか。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::isDefaultValueAvailable、getDefaultValue、isDefaultValueConstant、getDefaultValueConstantNameが内部関数にも使えるようになりました。
(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::isBuiltinはReflectionNamedTypeクラスに移動しました。
なぜなら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
SplFixedArrayがIteratorAggregateを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: aPHP7.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
substr、substr_count、substr_compare、iconv_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
vsprintf、vfprintf、vprintfの引数
$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_gpcとget_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|cPHP8.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_dumpとdebug_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 DEScryptは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
substr、mb_substr、iconv_substr、grapheme_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::repairStringとtidy::repairFileが静的メソッドになりました。
tidy::repairString('hoge'); // PHP8.0 エラー出ない // PHP7.4 Fatal error: Uncaught Error: Non-static method tidy::repairString() cannot be called staticallyTokenizer
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::openとXMLReader::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 3bcaddの第三引数は小数点以下の桁数なので、マイナス値は不適切です。
このように、型は合っているけど値が範囲外のときに使われる例外となります。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 nameNew 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 expressionthrowが式になりました。
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
expressionmatch式が導入されました。
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 > // PHP7.4 Parse error: syntax error, unexpected ':'引数の多い関数で最後の引数だけ呼んだり、引数の順番を入れ替えたりできます。
また呼び出し側に引数名を書くため、どの引数が何を表しているのか理解しやすくなります。Date
Added DateTime::createFromInterface() and DateTimeImmutable::createFromInterface()
DateTime::createFromInterface、DateTimeImmutable::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_add、enchant_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_pathとenchant_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_freeとenchant_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::getClass、ReflectionParameter::isArray、ReflectionParameter::isCallableが非推奨になりました。
かわりにReflectionParameter::getTypeとReflectionTypeを使いましょう。
Changed Functions
仕様が変更になる関数。
Reflection
ReflectionClass::getConstants and ReflectionClass::getReflectionConstants results can be now filtered
https://www.php.net/manual/ja/reflectionclass.getconstantsとReflectionClass::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::addGlobとZipArchive::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::addEmptyDir、ZipArchive::addFile、ZipArchive::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_contains、str_starts_with、str_ends_withが追加されました。
str_contains('森久保乃々', '森久保'); // true str_starts_with('望月聖', '望月'); // true str_ends_with('イヴ・サンタクロース', 'サンタクロース'); // trueそれぞれ○○が含まれる、○○で始まる/終わる、の文字列検索を楽にする関数です。
Added fdiv() function
fdivが追加されました。
echo fdiv(1, 0); // INFIEEE 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::setMtimeNameとZipArchive::setMtimeIndexが追加されました。
mtimeを操作できます。ZipArchive::setProgressCallback / ZipArchive::setCancelCallback
ZipArchive::setProgressCallbackとZipArchive::setCancelCallbackが追加されました。
ってUPGRADINGには書いてあるんだけど、試してみるとCall to undefinedって言われるしregisterProgressCallbackとregisterCancelCallbackが存在するのでおそらく間違い。
ZipArchive::replaceFile
ZipArchive::replaceFileが追加されました。
中身のファイルの一部を置き換えるみたいだけど、そんなややこしいことをPHPで行うことはあるのだろうか。ZipArchive::isCompressionMethodSupported / ZipArchive::isEncryptionMethodSupported
ZipArchive::isCompressionMethodSupportedとZipArchive::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
DatePeriodがIteratorAggregateをimplementsするようになりました。
(new ReflectionClass('DatePeriod'))->getInterfaceNames(); // [IteratorAggregate, Traversable]以前はTraversableでした。
DOM
DOMNamedNodeMap / DOMNodeList now implements IteratorAggregate
DOMNamedNodeMapおよびDOMNodeListがIteratorAggregateをimplementsするようになりました。
これらも以前はTraversableでした。Intl
IntlBreakIterator / ResourceBundle now implements IteratorAggregate
IntlBreakIteratorおよびResourceBundleがIteratorAggregateを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
imagepolygon、imageopenpolygon、imagefilledpolygonの引数
$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 passednullにしたり名前付き引数で飛ばしたりといったこともできません。
これはもっとどうにかしたほうがよかったのでは。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
PDOStatementがIteratorAggregateを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_resultがIteratorAggregateを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
SimpleXMLElementがRecursiveIteratorを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割減というところですね。
感想
変更点多すぎ!!!!
PHP8たいしたことないんじゃねえのとか言ってた奴土下座な。互換性のない変更の多くは、古くからのアバウトな仕様を排除し、厳密な書き方を推奨する方向に向けるものです。
implodeの引数が逆順でも書けるなんてのはその最たるものですね。
そのぶん昔からの熟成されたソースを使っているところは移行が大変になると思いますが、PHPは互換を切ってでも進化し続ける方向を選んだということなので、まあがんばってもらいましょう。
それにPHP7以降の近代風な書き方をしている限りでは、変更が必要なところはほとんどありません。そして新機能については、流行りの書式やら文法などを貪欲に取り入れていて、このあたりはさすがPHPってところです。
節操がないとも言えますが、その節操のなさこそがPHPたる所以と言えましょう。
アロー関数や型宣言、アトリビュートあたりはまだ機能が半端なので、次のメジャーバージョンかあるいはマイナーバージョンでさらに進化しそうですね。さらにJITが入ったことで、Webアプリケーションだけに囚われない、実用的な計算ライブラリでもPHPが輝く時代がやってくるかもしれません。
まあC++やGoあたりとはさすがに比べものにはならないですが、他のスクリプト言語に比べればだいぶいい線行っているのではないでしょうか。