- 投稿日:2020-08-10T23:05:15+09:00
AWS IoT CoreのShadowをPHPのSDKを用いて取得
AWS IoT CoreのShadowはIotDataPlaneClientのGetThingShadow()で取得できます。
公式サイトには以下サンプルが記載されています。
https://docs.aws.amazon.com/ja_jp/aws-sdk-php/v3/api/api-data.iot-2015-05-28.html#getthingshadow$result = $client->getThingShadow([ 'shadowName' => '<string>', 'thingName' => '<string>', // REQUIRED ]);しかし、$clientのオブジェクトの生成方法が書いていない。。。
$clientのオブジェクト生成方法は以下です。
$client = new IotDataPlaneClient( array( 'version' => 'latest', 'region' => 'ap-northeast-1', 'endpoint' => 'https://xxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com' ) );以上
- 投稿日:2020-08-10T22:01:29+09:00
ログイン機能と投稿機能の作成(PHP+MySQL)
記事の概要
作成したポートフォリオの解説です。以下をまとめています。
- 背景
- 主な機能
- 開発手順
- 工夫点
- 課題
背景
ポートフォリオ作成にあたり、「Udemy」というサービスを用いて基礎を勉強しました。
勉強期間1~2週間
https://www.udemy.com/course/completeweb2_jp/スペック
- 言語
- PHP 7.4.2
- javascript
- DBMS
- MySQL 5.7.26
- 開発環境
- MacOS Catalina 10.15.5
- MAMP 5.7
- ライブラリ
- jquery
- フレームワーク
- Bootstrap 4.2
- バージョン管理
- Git 2.24.3
- 本番環境
- xserver
主な機能
ログイン機能
投稿機能
左のレイヤーで全ユーザーの投稿を表示するか、自分の投稿のみを表示するか切り替えられる。
真ん中のレイヤーでは、ユーザー名と投稿が表示される。
右のレイヤーではつぶやきを投稿できる。
画面サイズが小さくなると右のレイヤーが下に回り込む。
開発手順
- 要件定義
- 環境設定
- データベース設計
- コーディング
- xserverデプロイ
1.用件定義
今回作成するアプリに必要な機能
・サインアップ
・サインイン
・セッション
・つぶやき投稿--余裕があれば--
・検索機能phpとDBを用いた構築を行う。
「udemy」で勉強したBootstrapも活用する。2.環境選定
基本的には「Udemy」で習った環境を使う。(上記スペックに記載)
本番環境では知名度の高い「xserver」を使用する。
GitとGitHubは練習として使っていく。3.データベース設計
正規化を意識して設計。調べたところTwitterのDBは検索性を高めるために投稿された月ごとにインデックすしてるようです。(今回はスルー)
4.コーディング
コーディングを実施
4.1データベース作成
MAMPのphpMyAdminを使ってデータベースを作成。
4.2ログイン画面
画面設計を簡易的にするためにBootstrapを使用。
サインアップとサインイン画面の切り替えはjQueryのtoggleを使用。
PDOを使ってMySQLに接続し、サインアップ及びサインインを行う。
未入力のバリテーションを実装。
ログイン時にセッションを使用。4.3メイン画面
Twitterをイメージして画面を3分割にした。
フレキシブルデザインになるようBootstrapを使用。
投稿されたテキストをユーザーネームと共に一覧で表示させる。
サンプルコード
home.php<div class="col-6"> <h2>つぶやき</h2> <?php displayTweets(); ?> <?php function displayTweets(){ global $pdo; $sql = "SELECT * FROM tweet LIMIT 30"; // SQLステートメントを実行し、結果を変数に格納 $stmt = $pdo->query($sql); // foreach文で配列の中身を一行ずつ出力 foreach ($stmt as $row) { ?> <div class="card"> <div class="card-header"> <!-- ユーザーネーム表示--> <?php showName($row['id']); ?> </div> <div class="card-body"> <h5 class="card-title"> <?php // データベースのフィールド名で出力 echo $row['text']; ?> </h5> <p class="card-text"></p> </div> </div> <?php } } ?> </div>5.xserverデプロイ
本番環境ではxserverを使用。
xserver上にDBを立て、プログラムもDB接続部分の修正。工夫点
・「Udemy」の教材ではDB接続にmysqli関数を用いていた。
時代はオブジェクト指向とのことなので、一から調べ、PDOを実装した。
・フレキシブルデザインを意識し、Bootstrapを用いた開発を行った。今後の課題
一通りの動作ができた時点で完成としました。
主な課題は以下の通りです。
- スマホだと見づらい
- パスワードの暗号化をしていない
- ログアウトボタンがない
- デザインがしょぼい
- jqueryとreact.jsの違いや使い方について調べる
参考文献
GitHubアカウント
- 投稿日:2020-08-10T21:47:08+09:00
PHP・GCの話-1話) なぜGarbageCollection? メモリーとGCを意識する
前書き
- すべての記事は、自分の勉強目的と主観の整理を含めています。あくまで参考レベルで活用してください。もし誤った情報などがあればご意見をいただけるととっても嬉しいです。
- 内容では、省略するか曖昧な説明で、わかりづらいところもあると思います。そこは、連絡いただければ補足などを追加するので、ぜひ負担なくご連絡ください。
- 本文での「GC」は、「Garbage Collection, Garbage Collector」の意味しており、略語として使われています。
- この記事は、連載を前提に構成されています。
※ 連載目録
- PHP・GCの話-1話)なぜGarbageCollection? メモリーとGCを意識する(←現在の記事)
- PHP・GCの話-2話)変数の管理情報、zval containerとreference count ⇨ 準備中
- PHP・GCの話-3話)変数データのメモリーからの消滅 ⇨ 準備中
- PHP・GCの話-4話)MemoryLeakと解除できない変数データ ⇨ 準備中
- PHP・GCの話-5話)GC登場。GC発生条件とroot buffer ⇨ 準備中
- PHP・GCの話-6話)管理対象の巡回・削除。Garbage Collection Cycle ⇨ 準備中
- PHP・GCの話-7話)GC関連機能紹介(GC Statistics, Weak Reference Type)(END) ⇨ 準備中
※ 連載で使うサンプルコード
● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。はじめに
今回は概論と本人の持論が主な話になります。メモリーとGCに対する知識を既にお持ちの方は、すぐに次の話から読んで頂いても良いと思います。2話からは、PHPの仕様に基づいた客観的な内容が主になります。
1. GCは何をするの?
GCとは、一言で「メモリー内のゴミ自動回収仕組み」と言えます。それを意識しながら内容をご覧いただけると良いと思います。
この概念は、日常生活のゴミ捨てとかなり似ています。
- 呼ばなくても、回収に来てくれる。(自動回収)
- 毎週、指定日に回収に来てくれる。(GC発動条件)もし、上記の仕組みがなかったら、私達はゴミ処理において相当苦しみを感じたと思います。
GCは、プログラムが使うメモリー空間において、ほぼ似たような役目を遂行します。
プログラム内のメモリー空間内のゴミ状態の参照や変数を、特定条件が達したタイミングで自動でメモリーから解除し、メモリー空間を広く確保してくれます。「エンジニアが明示的に解除しなくても」やってくれるのが核心です。
2. なぜGCを知る必要があるの?
1) PHPにおいてのGCは重要?
実のところPHPは、JAVA・TOMCAT基盤などのシ資源共有型のマルチスレッドステムとは違い、一つのリクエストに対して単体プロセスとして実行させ終了する形で使われる場合が多いです。
こういう単体プロセスで処理されるタイプの場合は、プロセス終了とともにすべてのメモリー専有が解除されるため、Memory LeakとGCに対するトラブルで大きい問題になる場合は少ないです。しかし最近のPHPは、
- パッケージやフレームワークをベースとした開発によりプログラムのサイズは増大
- バッチ系やデーモン系の大容量処理プログラムなどのニーズも多くなったことにより、PHPにおいてのメモリー管理の関心は高くなっていると思うので、軽く知っておいても良いと思っています。
2) GCは多くの言語においての「メモリー管理メカニズム」
GCは、プログラム開発分野において、ある程度「共通認識」になったと言っても過言では無いと思います。
プログラムを作ることにおいて、メモリー管理というのは、昔から多く語られる課題と言えます。
最近の開発言語は、エンジニアがメモリー管理に多く気にしなくてもいいように、仮想マシーンやインタプリタなどの言語プラットフォームが大半を管理してくれます。PHPはメモリー管理メカニズムの一つとして、GCを導入しています(Version 5.3以降)。
PHP以外にも、Java(Kotlin),C#、Python,Go, Rustなどなどの言語から、具現体により差はあるかもですが、大人気のJavascriptもまたGCのメカニズムを導入しています。3) それでもメモリーは有限である
Symfony\Component\ErrorHandler\Error\FatalError Allowed memory size of 134217728 bytes exhausted (tried to allocate 83886112 bytes)私達はプログラムを作る時、ある情報を保存・取得・処理・提供するため、様々な情報を変数としてアサインしています。
その情報はシステムマシーンの「メモリー」と言う空間に保存されます。そして変数の大きさに比例し一定の空間を占有します。
しかし、メモリーは物理的な空間であり、容量の限界があります。
もしメモリーが足りなくなり、それを放置すればシステムはダウン状態に落ち、トラブルになります。特に最近は、開発トレンドが、フレームワークやパッケージ組み合わせ開発が主類になっています。
そこで生産性と大型システム開発の容易性は飛躍的に上がりましたが、必要以上の機能を搭載することにより平均的にはプログラムが要求するシステム資源もまた上がっています。PHP、OSや、クラウドサービス(例:AWS)などでそれを防ぐために用意された仕組みが色々あります。
しかし、それだけに依存するのは制約・費用・運用面で、長期的に色々デメリットを起こす可能性が高いので、すごく損することだと言えます。4) GCも万能の神様ではない
GCも万能ではなく、エンジニアがメモリーを全く気にしない実装をすると、時々はすごく難題の欠陥を作ったりします。自分が考えるたとえとしては以下の3つがあります。
PHPでは、要請を独立したプロセスで処理する場合が多いので、影響は少ないかもですが、無関係では無いところと、他の言語でのGCを見る観点としては、役に立つと思うので、参考までに御覧ください。
● GCの発動条件。常に発動するわけではない。
言語によってGCの発動条件はそれぞれ違いますが、常に発動しているわけではありません。
つまり、GCが発動する前に、メモリーが足りなくなり、プログラムが落ちてしまうことは全然ありです。PHPの例えを簡単に引用すると、PHPはメモリー解除候補が10000個に達した条件でGCが発動しますが、条件に達する前にメモリー制限の容量まで達してしまうと、プログラムがMemoryLimitErrorで落ちてしまいます。
(PHPのGC発動条件に対しては、後ほど詳しく扱う予定です。)● GCが発生すると言うのはすでにメモリー空間においての赤信号!
GCの発生条件ともつながる話ですが、GCが発生するという状態は、「ついにメモリーを回収しないと行けない状況になってしまった」という状態であることを肝に銘じる必要があります。
※1
● GCが発動して終わるまで、プログラムがフリーズするか、システム負荷が相当かかる!
GCの発生と遂行は、プログラムにおいての最優先処理事項として遂行されます。
つまり、該当プロセスのすべての作業を止めて、GCを遂行するということを意味します。GCで回収している間は、プログラムがメモリー空間を使ってしまったら整合性が崩れるからです。
しかしGCは相当重い処理であり、時々相当時間がかかってしまう場合もあります。GCに時間がかかっている間、プログラムはFreeze状態になります。
それは、つまり処理の遅延を意味し、ユーザーサービスにおいては、敏感な問題になります。※1
5) メモリー最適化は、関心度が高い課題
PHPに例えるなら、
- Version 5.3から、GCメカニズムを導入し、
- Version 7では、Weak Reference Typeを提供し、
- Version 8では、WeakMap Typeを提供する予定(RFC確定)で、PHP内でもメモリー最適化の関心度は高いと思っています。
そして、PHPだけじゃなく開発全般において、関心度が高いものだと言えます。それを少しでも意識しておくことは、開発全般においてもメリットになると思ってます。
6) メモリーとGCを少しでも意識した上で、プログラムを作り運用する
メモリーとGCを意識したシステム作りと運用は、長期的にマシーン性能にかかるコストと、メモリー関連問題発生時の運用コストの節減につながると思います。
特にメモリーに関わる欠陥問題は、事前検知が難しい場合も多く、起きたとしても原因特定と解決に苦難する場合が多いです。
なので、メモリーとGCを意識するのは、
- 難しい欠陥問題に対する予防策であり、
- 問題が起きたときの対処ノウハウにもつながる。のではないでしょうか。
3. 「PHP・GCの話」では何をやるの?
1) 事前に知っておいたらいいのは?
以下の内容を事前に熟知しておくと、記事を見ることに役立つと思います。
- PHPとOOPの基本概念
- 変数・参照変数の基本概念
- システムマシーンのメモリーの基本概念
- GCの基本概念(当記事や、Wikipediaなどの内容でOK、1ページ以下で収まるような内容がおすすめ)2) 連載で扱う内容のオーバービュー
- PHP・GCの話-1話)なぜGarbageCollection? メモリーとGCを意識する
- GCの役目、GCをなぜ知る必要があるかの持論と概論で構成されています。
- PHP・GCの話-2話)変数の管理情報、zval containerとreference count
- PHPの変数管理のメカニズムと、参照カウントの概念を説明します。
- PHP・GCの話-3話)変数データのメモリーからの消滅
- 変数のデータが消滅してメモリーが確保される条件を説明します。
- PHP・GCの話-4話)MemoryLeakと解除できない変数データ
- 無効になってもなお、メモリーから解除されず、残り続ける現象について説明します。
- PHP・GCの話-5話)GC登場。GC発生条件とroot buffer
- GCの役目、発動条件、GC監視対象を保存するroot buffer仕様を説明します。
- PHP・GCの話-6話)管理対象の巡回・削除。Garbage Collection Cycle
- GCがどういうメカニズムでGC監視対象のデータを巡回し、解除するとかを説明します。
- PHP・GCの話-7話)GC関連機能紹介(GC Statistics, Weak Reference)(END)
- GC分析ツールと、Weak Reference(>PHP7.4), WeakMap(>PHP8)を簡単に説明します。
3) 参考した資料は?
主ににPHP MANUALを参考にしています。
https://www.php.net/manual/en/features.gc.php
https://www.php.net/manual/en/langref.php4) 連載に使うサンプルコード
● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。GCに関する本連載記事は、基本的にこのサンプルコードをベースにPHP-GCの特徴をはなしていきます。
サンプルコードは、必ず見る必要も実行してみる必要もありません。
各話ごとに、コードを分解して動作原理と結果を解説しますので、基本記事の内容で足りるように心がけます。
あくまで、全体コードをみたい、手元で回してみたい、修正して回してみたいという方向けです。そして、このサンプルコードは、あくまでGC説明のため用意されたコードなので、書き方としてはよくないパターンもあります。
コードの完成度よりも、GCの動作理解の参考までにご活用ください。加えて、本コードはlaravelフレームワークをベースに作成されていますが、Laravel自体を詳しく知る必要はありません。そして、あくまでGCの開設のため書かれたコードで、実用性のあるコードではあることをご理解ください。
反面、そのコードを見て、
- コード動作で、内部でどういうものが起きるのか?
- どういうケースになれば、メモリーが足りなくなり、問題が発生しそうなのか?
- 使われた参照変数はどういう関係図になるのか?
- どこで変数の有効範囲が切れるか?メモリーリークはどこで起きるか?
- どういう絵図で、zvalとroot zval, Garbage Collection CycleのGC対象巡回が行われるのか?
- 弱参照タイプとは?
に関してすでにご存知の方は、当連載記事の大半の内容をすでにご存知の方なので、読む必要はあんまり無いかもしれません。
Summary
今回で、最低限に覚えて頂くと良い内容は以下になります。
- GCは「メモリー・自動回収仕組み」
- GCは、プログラム開発においての共通認識
- メモリーは有限である
- GCも万能ではない今回の後書き
今回は主観的持論や概論を主に話しましたが、こういう系の記事が一番むずかしい気がします。
なので自分としては、今の記事の細かな内容よりも、「GCの役目」と「なぜGCを知る必要があるか?」の問を心のどこかでとどめていただければ、嬉しいと思っております。次回からは、主にPHPに絞ったGCの話をやっていきます。実際のサンプルコードを使いづつ、今回の話よりは客観的な内容になると思います。だからこそGCに対して、読者自身の主観を持った上で見ていただけることで、より効果的な情報共有することができるという思いで、今回の話を書いてみました。
では、次回も頑張って整理していきたいと思います。
※注釈
※1
▶ 「ついにメモリーを回収しないと行けない状況になってしまった」という状態であることを肝に銘じる必要があります。比較的に最近までは、GCが起きることは赤信号という風にみることもでき、特に古いシステムではよくトラブルになるポイントとなっていました。
しかし、厳密にいうとこの内容は、もう100%当てはまりません。最近はJavaのG1GC(Garbage First GC)は短い周期でGCを起こし、オーバーヘットを最低限にするとかもしており、JAVA1.9からはデフォルトで推奨されています。近いうちには、この内容が大きく変わるかもしれません。
- 投稿日:2020-08-10T21:47:08+09:00
PHP・GCの話-1話) なぜGarbageCollection? メモリとGCを意識する
前書き
- すべての記事は、自分の勉強目的と主観の整理を含めています。あくまで参考レベルで活用してください。もし誤った情報などがあればご意見をいただけるととっても嬉しいです。
- 内容では、省略するか曖昧な説明で、わかりづらいところもあると思います。そこは、連絡いただければ補足などを追加するので、ぜひ負担なくご連絡ください。
- 本文での「GC」は、「Garbage Collection, Garbage Collector」の意味しており、略語として使われています。
- この記事は、連載を前提に構成されています。
※ 連載目録
- PHP・GCの話-1話)なぜGarbageCollection? メモリとGCを意識する(←現在の記事)
- PHP・GCの話-2話)変数の管理情報、zval containerとreference count ⇨ 準備中
- PHP・GCの話-3話)変数データのメモリからの消滅 ⇨ 準備中
- PHP・GCの話-4話)MemoryLeakと解除できない変数データ ⇨ 準備中
- PHP・GCの話-5話)GC登場。GC発生条件とroot buffer ⇨ 準備中
- PHP・GCの話-6話)管理対象の巡回・削除。Garbage Collection Cycle ⇨ 準備中
- PHP・GCの話-7話)GC関連機能紹介(GC Statistics, Weak Reference Type)(END) ⇨ 準備中
※ 連載で使うサンプルコード
● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。はじめに
今回は概論と本人の持論が主な話になります。メモリとGCに対する知識を既にお持ちの方は、すぐに次の話から読んで頂いても良いと思います。2話からは、PHPの仕様に基づいた客観的な内容が主になります。
1. GCは何をするの?
GCとは、一言で「メモリ内のゴミ自動回収仕組み」と言えます。それを意識しながら内容をご覧いただけると良いと思います。
この概念は、日常生活のゴミ捨てとかなり似ています。
- 呼ばなくても、回収に来てくれる。(自動回収)
- 毎週、指定日に回収に来てくれる。(GC発動条件)もし、上記の仕組みがなかったら、私達はゴミ処理において相当苦しみを感じたと思います。
GCは、プログラムが使うメモリ空間において、ほぼ似たような役目を遂行します。
プログラム内のメモリ空間内のゴミ状態の参照や変数を、特定条件が達したタイミングで自動でメモリから解除し、メモリ空間を広く確保してくれます。「エンジニアが明示的に解除しなくても」やってくれるのが核心です。
2. なぜGCを知る必要があるの?
1) PHPにおいてのGCは重要?
実のところPHPは、JAVA・TOMCAT基盤などのシ資源共有型のマルチスレッドステムとは違い、一つのリクエストに対して単体プロセスとして実行させ終了する形で使われる場合が多いです。
こういう単体プロセスで処理されるタイプの場合は、プロセス終了とともにすべてのメモリ専有が解除されるため、Memory LeakとGCに対するトラブルで大きい問題になる場合は少ないです。しかし最近のPHPは、
- パッケージやフレームワークをベースとした開発によりプログラムのサイズは増大
- バッチ系やデーモン系の大容量処理プログラムなどのニーズも多くなったことにより、PHPにおいてのメモリ管理の関心は高くなっていると思うので、軽く知っておいても良いと思っています。
2) GCは多くの言語においての「メモリ管理メカニズム」
GCは、プログラム開発分野において、ある程度「共通認識」になったと言っても過言では無いと思います。
プログラムを作ることにおいて、メモリ管理というのは、昔から多く語られる課題と言えます。
最近の開発言語は、エンジニアがメモリ管理に多く気にしなくてもいいように、仮想マシーンやインタプリタなどの言語プラットフォームが大半を管理してくれます。PHPはメモリ管理メカニズムの一つとして、GCを導入しています(Version 5.3以降)。
PHP以外にも、Java(Kotlin),C#、Python,Go, Rustなどなどの言語から、具現体により差はあるかもですが、大人気のJavascriptもまたGCのメカニズムを導入しています。3) それでもメモリは有限である
Symfony\Component\ErrorHandler\Error\FatalError Allowed memory size of 134217728 bytes exhausted (tried to allocate 83886112 bytes)私達はプログラムを作る時、ある情報を保存・取得・処理・提供するため、様々な情報を変数としてアサインしています。
その情報はシステムマシーンの「メモリ」と言う空間に保存されます。そして変数の大きさに比例し一定の空間を占有します。
しかし、メモリは物理的な空間であり、容量の限界があります。
もしメモリが足りなくなり、それを放置すればシステムはダウン状態に落ち、トラブルになります。特に最近は、開発トレンドが、フレームワークやパッケージ組み合わせ開発が主類になっています。
そこで生産性と大型システム開発の容易性は飛躍的に上がりましたが、必要以上の機能を搭載することにより平均的にはプログラムが要求するシステム資源もまた上がっています。PHP、OSや、クラウドサービス(例:AWS)などでそれを防ぐために用意された仕組みが色々あります。
しかし、それだけに依存するのは制約・費用・運用面で、長期的に色々デメリットを起こす可能性が高いので、すごく損することだと言えます。4) GCも万能の神様ではない
GCも万能ではなく、エンジニアがメモリを全く気にしない実装をすると、時々に、すごく難題の欠陥を作ったりします。自分が考えるたとえとしては以下の3つがあります。
PHPでは、要請を独立したプロセスで処理する場合が多いので、影響は少ないかもですが、無関係では無いところと、他の言語でのGCを見る観点としては、役に立つと思うので、参考までにご覧ください。
● GCの発動条件。常に発動するわけではない。
言語によってGCの発動条件はそれぞれ違いますが、常に発動しているわけではありません。
つまり、GCが発動する前に、メモリが足りなくなり、プログラムが落ちてしまうことは全然ありです。PHPの例えを簡単に引用すると、PHPはメモリ解除候補が10000個に達した条件でGCが発動しますが、条件に達する前にメモリ制限の容量まで達してしまうと、プログラムがMemoryLimitErrorで落ちてしまいます。
(PHPのGC発動条件に対しては、後ほど詳しく扱う予定です。)● GCが発生すると言うのはすでにメモリ空間においての赤信号!
GCの発生条件ともつながる話ですが、GCが発生するという状態は、「ついにメモリを回収しないと行けない状況になってしまった」という状態であることを肝に銘じる必要があります。
※1
● GCが発動して終わるまで、プログラムがフリーズするか、システム負荷が相当かかる!
GCの発生と遂行は、プログラムにおいての最優先処理事項として遂行されます。
つまり、該当プロセスのすべての作業を止めて、GCを遂行するということを意味します。GCで回収している間は、プログラムがメモリ空間を使ってしまったら整合性が崩れるからです。
しかしGCは相当重い処理であり、時々相当時間がかかってしまう場合もあります。GCに時間がかかっている間、プログラムはFreeze状態になります。
それは、つまり処理の遅延を意味し、ユーザーサービスにおいては、敏感な問題になります。※1
5) メモリ最適化は、関心度が高い課題
PHPに例えるなら、
- Version 5.3から、GCメカニズムを導入し、
- Version 7では、Weak Reference Typeを提供し、
- Version 8では、WeakMap Typeを提供する予定(RFC確定)で、PHP内でもメモリ最適化の関心度は高いと思っています。
そして、PHPだけじゃなく開発全般において、関心度が高いものだと言えます。それを少しでも意識しておくことは、開発全般においてもメリットになると思ってます。
6) メモリとGCを少しでも意識した上で、プログラムを作り運用する
メモリとGCを意識したシステム作りと運用は、長期的にマシーン性能にかかるコストと、メモリ関連問題発生時の運用コストの節減につながると思います。
特にメモリに関わる欠陥問題は、事前検知が難しい場合も多く、起きたとしても原因特定と解決に苦難する場合が多いです。
なので、メモリとGCを意識するのは、
- 難しい欠陥問題に対する予防策であり、
- 問題が起きたときの対処ノウハウにもつながる。のではないでしょうか。
3. 「PHP・GCの話」では何をやるの?
1) 事前に知っておいたらいいのは?
以下の内容を事前に熟知しておくと、記事を見ることに役立つと思います。
- PHPとOOPの基本概念
- 変数・参照変数の基本概念
- システムマシーンのメモリの基本概念
- GCの基本概念(当記事や、Wikipediaなどの内容でOK、1ページ以下で収まるような内容がおすすめ)2) 連載で扱う内容のオーバービュー
- PHP・GCの話-1話)なぜGarbageCollection? メモリとGCを意識する
- GCの役目、GCをなぜ知る必要があるかの持論と概論で構成されています。
- PHP・GCの話-2話)変数の管理情報、zval containerとreference count
- PHPの変数管理のメカニズムと、参照カウントの概念を説明します。
- PHP・GCの話-3話)変数データのメモリからの消滅
- 変数のデータが消滅してメモリが確保される条件を説明します。
- PHP・GCの話-4話)MemoryLeakと解除できない変数データ
- 無効になってもなお、メモリから解除されず、残り続ける現象について説明します。
- PHP・GCの話-5話)GC登場。GC発生条件とroot buffer
- GCの役目、発動条件、GC監視対象を保存するroot buffer仕様を説明します。
- PHP・GCの話-6話)管理対象の巡回・削除。Garbage Collection Cycle
- GCがどういうメカニズムでGC監視対象のデータを巡回し、解除するとかを説明します。
- PHP・GCの話-7話)GC関連機能紹介(GC Statistics, Weak Reference)(END)
- GC分析ツールと、Weak Reference(>PHP7.4), WeakMap(>PHP8)を簡単に説明します。
3) 参考した資料は?
主ににPHP MANUALを参考にしています。
https://www.php.net/manual/en/features.gc.php
https://www.php.net/manual/en/langref.php4) 連載に使うサンプルコード
● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。GCに関する本連載記事は、基本的にこのサンプルコードをベースにPHP-GCの特徴をはなしていきます。
サンプルコードは、必ず見る必要も実行してみる必要もありません。
各話ごとに、コードを分解して動作原理と結果を解説しますので、基本記事の内容で足りるように心がけます。
あくまで、全体コードをみたい、手元で回してみたい、修正して回してみたいという方向けです。そして、このサンプルコードは、あくまでGC説明のため用意されたコードなので、書き方としてはよくないパターンもあります。
コードの完成度よりも、GCの動作理解の参考までにご活用ください。加えて、本コードはlaravelフレームワークをベースに作成されていますが、Laravel自体を詳しく知る必要はありません。そして、あくまでGCの開設のため書かれたコードで、実用性のあるコードではあることをご理解ください。
反面、そのコードを見て、
- コード動作で、内部でどういうものが起きるのか?
- どういうケースになれば、メモリが足りなくなり、問題が発生しそうなのか?
- 使われた参照変数はどういう関係図になるのか?
- どこで変数の有効範囲が切れるか?メモリリークはどこで起きるか?
- どういう絵図で、zvalとroot zval, Garbage Collection CycleのGC対象巡回が行われるのか?
- 弱参照タイプとは?
に関してすでにご存知の方は、当連載記事の大半の内容をすでにご存知の方なので、読む必要はあんまり無いかもしれません。
Summary
今回で、最低限に覚えて頂くと良い内容は以下になります。
- GCは「メモリ・自動回収仕組み」
- GCは、プログラム開発においての共通認識
- メモリは有限である
- GCも万能ではない今回の後書き
今回は主観的持論や概論を主に話しましたが、こういう系の記事が一番むずかしい気がします。
なので自分としては、今の記事の細かな内容よりも、「GCの役目」と「なぜGCを知る必要があるか?」の問を心のどこかでとどめていただければ、嬉しいと思っております。次回からは、主にPHPに絞ったGCの話をやっていきます。実際のサンプルコードを使いづつ、今回の話よりは客観的な内容になると思います。だからこそGCに対して、読者自身の主観を持った上で見ていただけることで、より効果的な情報共有することができるという思いで、今回の話を書いてみました。
では、次回も頑張って整理していきたいと思います。
※注釈
※1
▶ 「ついにメモリを回収しないと行けない状況になってしまった」という状態であることを肝に銘じる必要があります。比較的に最近までは、GCが起きることは赤信号という風にみることもでき、特に古いシステムではよくトラブルになるポイントとなっていました。
しかし、厳密にいうとこの内容は、もう100%当てはまりません。最近はJavaのG1GC(Garbage First GC)は短い周期でGCを起こし、オーバーヘットを最低限にするとかもしており、JAVA1.9からはデフォルトで推奨されています。近いうちには、この内容が大きく変わるかもしれません。
- 投稿日:2020-08-10T18:19:37+09:00
Laravelのインストール、初期設定、GitHubの使用
Laravelのインストール
Laravelのインストールには専用インストーラを使用する方法とComposerを利用する方法の2つがありますが、今回はComposerでのインストールを行います。
DBはMySQLを使います。
PHPとphpmyadminが使える環境を前提に進めます。Composerのインストール
ComposerはPHPのパッケージ管理ツールです。
下記のサイトからインストールできます。
ComposerComposerのインストール確認
下記のコマンドを実行してバージョンが表示されればインストールできています。
terminalcomposer --version //composer -vでも可Composerのパッケージインストール時間の短縮
packagist.jpの使用
海外に向いているリポジトリを、日本の有志の方が運用をおこなっているリポジトリ( https://packagist.jp/ )に向ける事でインストールにかかる時間が短縮できます。
リポジトリの追加
terminalcomposer config -g repos.packagist composer https://packagist.jp composer updateリポジトリの削除
terminalcomposer config -g --unset repos.packagistPrestissimoの使用
Prestissimoを導入してダウンロードを並列処理する
Prestissimoterminalcomposer global require hirak/prestissimoComposerを使ったLaravelのインストール
terminal//最新版をインストールする場合 composer create-project laravel/laravel [プロジェクト名] --prefer-dist //バージョンを指定してインストールする場合(x.0の部分がバージョン指定) composer create-project laravel/laravel [プロジェクト名] --prefer-dist "x.0.*"※ --prefer-distはファイル圧縮版を使用するというオプションです。
Laravelの動作確認(簡易サーバー)
プロジェクトのルートに移動して下記コマンドを実行します。
terminalphp artisan servehttp://localhost:8000/ もしくは http://127.0.0.1:8000 にアクセスして、Laravelのデフォルトの画面が表示されればOKです。
初期設定
タイムゾーン、言語を日本にする
config/app.php'timezone' => 'UTC', //↓ 'timezone' => 'Asia/Tokyo', 'locale' => 'en', //↓ 'locale' => 'ja',DBの文字コードをutf8にする
config/database.php'mysql' => [ 'charset' => 'utf8mb4', //↓ 'charset' => 'utf8', 'collation' => 'utf8mb4_unicode_ci', //↓ 'collation' => 'utf8_unicode_ci', ]デバッグバーの使用
インストール
簡易サーバーを立ち上げると下部にデバッグ情報が表示されるようになります。
terminalcomposer require barryvdh/laravel-debugbar非表示
本番環境など表示したくない環境では下記のように値を変更します。
.envAPP_DEBUG=true //↓ APP_DEBUG=falseDB(MySQL)の設定
phpMyAdminを使ってDB(DB名)とユーザー(ユーザー名、パスワード)を作成して、下記を編集します。
.envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=DB名 DB_USERNAME=ユーザー名 DB_PASSWORD=パスワードLaravel-ui、認証
必要なければ飛ばしても大丈夫です。
terminal//バージョン指定しない場合 composer require laravel/ui --dev //バージョン指定する場合 composer require laravel/ui:^1.0 --dev //スカフォールド生成 php artisan ui bootstrap php artisan ui vue php artisan ui react //スカフォールド生成+ユーザー認証 (認証が必要なときはこちら) php artisan ui bootstrap --auth php artisan ui vue --auth php artisan ui react --authnpmでのパッケージのインストール
Please run "npm install && npm run dev" to compile your fresh scaffolding.と表示されるので従います。
npmがインストールされていない場合、Node.jsをインストールします。
(Node.jsがインストールされていればnpmも使えるようになります。)
Node.jsNode.js、npmのインストール確認
バージョンが表示されればインストールできています。
terminalnode -v npm –vnpmによるパッケージインストール
package.jsonの内容に従ってパッケージがインストールされます。
terminalnpm ciCSSとJavaScriptのビルド
terminal//一回だけビルド npm run dev //ファイルの変更を感知してビルド npm run watchビルド対象のファイル、ビルドして生成されるファイルはwebpack.mix.jsで確認、変更できます。
種類 ビルドの対象ファイル JavaScript resources/js/app/js SCSS resources/sass/app.scss
種類 ビルドで生成されるファイルが格納されるフォルダ JavaScript public/js CSS public/css migration
3つのテーブルが作成されます。
(上で認証機能を追加している場合はusersテーブルを加えた4つのテーブルが作成されます。)terminalphp artisan migrate作成されるテーブル
- failed_jobs
- migrations
- password_resets
- (users)
Laravelの動作確認(簡易サーバー)
プロジェクトのルートに移動して下記コマンドを実行します。
terminalphp artisan servehttp://localhost:8000/ もしくは http://127.0.0.1:8000 にアクセスして、Laravelのデフォルトの画面の右上にLOGINとREGISTERが追加されていればOKです。
エラーメッセージの日本語化
resources/lang/に下記のjaフォルダをenフォルダと同じ階層に配置します。
https://github.com/minoryorg/laravel-resources-lang-ja※jaフォルダ内のファイルの編集でさらにメッセージのカスタマイズができます。
resources\lang\ja\validation.php'attributes' => [], //↓ 'attributes' => ['email'=>'メールアドレス', 'name'=>'名前' ],GitHubの使用
GitHubへpushする
GitHubでリポジトリ作成
GitHubのマイページにあるRepositoriesタブを開き、Newボタンを押します。
リポジトリ名を入力して、空っぽの新規レポジトリを作成しておきます。ローカルでのGitのリポジトリ作成
プロジェクトルートに移動してGitのリポジトリを作成します。
terminalgit init.gitignore
Laravelでは、gitを使う前提で.gitignoreがディレクトリ直下に用意されています。
無視ファイル/フォルダ(一部抜粋) 内容 .env 接続情報などが保存されているので、誤ってGitHubに上がらないように設定されている /vender composerでインストールしたファイル (laravel自体もここに入っている) /node_modules npmでインストールしたファイルが入っている push
terminalgit add . git commit -m "メッセージ" git remote add origin https://github.com/ユーザー名/リポジトリ名.git git push -u origin masterGitHubからcloneする
今度はGitHubからファイルを取ってきて使えるようにしていきます。
terminalgit clone https://github.com/ユーザー名/リポジトリ名.gitcloneしただけでは、Laravelの動作に必要なファイルが足りないので、.gitignoreで無視していたファイルを作成します。
.env
DBの接続情報
.env.exampleをコピーして.envにリネームして、接続情報などを記入します。
DBがなければ作成が必要になります。デバッグバーの使用
使用する場合のみ変更します。
APP_DEBUG=false //↓ APP_DEBUG=trueアプリケーションキーの作成
terminalphp artisan key:generate.envのAPP_KEYに値が書き込まれます。
/vender配下のファイル (Composerでのパッケージインストール)
cloneした環境にComposerがインストールがされていない場合はインストールします。
下記コマンドでcomposer.jsonの内容に従ってパッケージがインストールされます。terminalcomposer install/node_modules配下のファイル (npmでのパッケージインストール)
cloneした環境にnpmがインストールがされていない場合はインストールします。
下記コマンドでpackage.jsonの内容に従ってパッケージがインストールされます。terminalnpm cimigration
テーブルを作成します。
terminalphp artisan migrateLaravelの動作確認(簡易サーバー)
プロジェクトのルートに移動して下記コマンドを実行します。
terminalphp artisan serve設定変更が反映されない場合
キャッシュやコンフィグのクリアを試してみてください。
terminalphp artisan cache:clear php artisan config:clear
- 投稿日:2020-08-10T18:05:10+09:00
PHPの配列は値が変更されるまで複製されないことの確認
PHPの配列は擬似基本型
PHPにおいて配列はオブジェクトのような参照型ではなく
数値や文字列と同じように基本型のように振る舞うしかし内部的には参照型であり
値が変更されることで値が複製されることによって
あたかも基本型であるような振る舞いをしているだけである確認
まずは大きめの配列を作って
同じ配列を10回別の配列に追加してみる<?php echo "first:".(memory_get_usage()>>8)."KB<br/>"; $arr = []; for($i=0;$i<10000;$i++) { $arr[] = $i; } $arr2=[]; for($i=0;$i<10;$i++) { $arr2[$i] = $arr; } echo "now:".(memory_get_usage()>>8)."KB<br/>"; echo "peak:".(memory_get_peak_usage()>>8)."KB"; /* 結果 first:1553KB now:3620KB peak:3620KB */別の配列に追加する数を10回から100回に増やしてみる
<?php echo "first:".(memory_get_usage()>>8)."KB<br/>"; $arr = []; for($i=0;$i<10000;$i++) { $arr[] = $i; } $arr2=[]; for($i=0;$i<10;$i++) { $arr2[$i] = $arr; } echo "now:".(memory_get_usage()>>8)."KB<br/>"; echo "peak:".(memory_get_peak_usage()>>8)."KB"; /* 結果 first:1553KB now:3649KB peak:3649KB */10回:3620KB
100回:3649KBほとんど変動がない
次に別の配列に値を変更しながら100回追加してみる
echo "first:".(memory_get_usage()>>8)."KB<br/>"; $arr = []; for($i=0;$i<10000;$i++) { $arr[] = $i; } $arr2=[]; for($i=0;$i<100;$i++) { $arr2[$i] = $arr; $arr2[$i][0]++; } echo "now:".(memory_get_usage()>>8)."KB<br/>"; echo "peak:".(memory_get_peak_usage()>>8)."KB"; /* 結果 first:1554KB now:210072KB peak:210072KB */変更せずに100回:3649KB
変更しながら100回:210072KB劇的にメモリ使用量が増えた
つまり何?
PHPのメモリの節約のためには無用に配列の値を変更することを避けるべきである
値を加工するなら取り出してからやったほうが良いfunction hoge($huge_array){ $huge_array['value'] = some_modify($huge_array['value']); } ↓ function hoge($huge_array){ $value = some_modify($huge_array['value']); }PHPで配列が複製されるのを防ぐために参照変数を用いることは無意味である
値を変更しさえしなければ配列は複製されないfunction hoge(&$array){ ... } ↓ function hoge($array){ ... }
- 投稿日:2020-08-10T17:00:17+09:00
Docker × Laravel メールの送信処理をローカルで確認する
ウェブサーバー、アプリケーションサーバー、データベースサーバーが用意されていれば最低限Laravelを動作させることはできますが、実際に開発を進めてくるとメールを送信する処理を書くことが多くあります。
ローカルでのメール確認のためにメールサーバーを用意して、たくさんのメールアカウントを作って、メールを送信して、各メールアカウントでログインして確認...するのはとても非効率です。
そこでメール送信テストツールのMailHogというツールが公式のDockerイメージが提供されています。
こちらを利用すると実際にメールを送信することなく、メール内容をWeb UIで確認できます。MailHog
開発者向けのメールテストツール
前提
最強のLaravel開発環境をDockerを使って構築する【新編集版】
当記事は上記の記事の補足になる記事です。
Laravel環境構築
$ git clone git@github.com:ucan-lab/docker-laravel.git $ cd docker-laravel/infrastructure $ make create-projectまずはサクッと環境構築します。
環境
- PHP 7.4.6
- Laravel 7.21.0
手順
infrastructure/docker-compose.yml を編集する
$ infrastructure $ docker-compose down※
docker-compose.yml
やDockerfile
を変更する場合は予めコンテナを破棄しておくと良いです。docker-compose.ymlservices: mail: image: mailhog/mailhog ports: - 8025:8025
services
配下に追記します。
特にカスタマイズは必要ないので、公式のMailHogイメージをそのまま利用します。$ docker-compose up -dコンテナを作成して起動します。
$ docker-compose ps Name Command State Ports ------------------------------------------------------------------------------------------------- docker-laravel_app_1 docker-php-entrypoint php-fpm Up 9000/tcp docker-laravel_db_1 docker-entrypoint.sh mysqld Up 0.0.0.0:3306->3306/tcp, 33060/tcp docker-laravel_mail_1 MailHog Up 1025/tcp, 0.0.0.0:8025->8025/tcp docker-laravel_web_1 nginx -g daemon off; Up 0.0.0.0:80->80/tcp
backend/.env
.envMAIL_HOST=mail MAIL_PORT=1025 MAIL_FROM_ADDRESS=info@example.com環境変数にMailHogの設定を追記します。
ホスト名には
それと送信元アドレスの設定が必要です。メール送信テスト
$ docker-compose exec app php artisan tinker Mail::raw('test mail',function($message){$message->to('test@example.com')->subject('test');});参考
- 投稿日:2020-08-10T16:24:21+09:00
PHPパフォーマンスチューニング
夏休み記事2件目です。
PHPのパフォーマンスチューニングする場合、どの部分がボトルネックになっているか把握しないといけません。
今回は、Xdebugとwebgrindを使ったプロファイリングをやってみたのでまとめます。
なお、本記事の画像は、プログラムの情報が漏れないようぼかしています。とりあえずどのように見えるのか紹介
Xdebugでphpのプロファイリングをしてそのアウトプットをwebgrindで表示すると以下のような感じになります。
- 実行された関数名
- 呼び出し回数
- その関数自身の全体に対する処理時間の割合(%)、または実行時間(msec / usec)
- 実行するのにかかった総時間
が表示されます。
処理の流れをわかりやすくグラフで表示することもできます。
それでは、使用するための準備をしていきます。
なお、本記事の実行環境は、以下の通りです。
- プロファイリングしたい対象サーバ(docker)
- centOS
- Apache
Xdebug
インストール
プロファイリングしたい物が入っているサーバーで、以下のように実行していきます。
bash# ここは使いたいPHPのバージョンにあったものを選択してください(https://xdebug.org/download/historical) curl -OL https://xdebug.org/files/xdebug-2.2.0RC1.tgz tar -xzf xdebug-2.2.0RC1.tgz cd xdebug-2.2.0RC1 phpize ./configure --enable-xdebug make make install最後の
make install
したときの出力にモジュールの場所が出てきるので、これをメモってください。
(例)/usr/lib64/php/modules/xdebug.so
これで、Xdebugのインストールは終了です。設定と有効化
それではこれを使用するために、
php.ini
をいじっていきます。
もし、php.ini
の場所がわからない場合は、以下のものを実行するとわかります。bashphp --ini
場所がわかったところで、php.iniを編集していきます。
php.ini; ファイルの末尾に以下を追加します。 zend_extension="(さっきメモったパス)/xdebug.so" ; プロファイリングを有効化 xdebug.profiler_enable = 1 ; プロファイルの結果を吐き出す場所の指定(デフォルトは/tmpなのでなくても良い) xdebug.profiler_output_dir=/tmpプロファイリング
ここまでやったら、webサーバーを再起動しましょう
bash# apacheの例 service httpd restart
これで、プロファイリングしたいページを見ると、先ほど結果を吐き出す場所に指定したところにプロファイリング結果が出ていると思います。
続いて、この結果を見やすくするためにwebgrindを準備していきたいと思います。webgrindの準備
ここにインストール手順が載っています。
今回は、プロファイリング対象とは別で使いたいので、dockerを使用してみます。bashdocker run --rm -v /(ここにプロファイル結果が保存されているパスを指定):/tmp -p 80:80 jokkedk/webgrind:latest見やすく表示してみる
これでwebgrindは使えます。
プロファイリング対象と同じサーバーで動かしたい場合はここを見てやってみてください。それでは、http://localhost:80/index.phpにアクセスすると、以下のような画面が出てくると思います。
右上の機能の説明は左から、
- show:(処理時間がかかった順に)どれほど表示するか(100%にすると、全ての処理が表示されます。)
- Auto(newest):表示させるプロファイリング結果、デフォルトは、一番新しいファイル
- percent:表示単位を(percent / milliseconds / microseconds)から選択
これで
update
を押すと、設定が反映されます。
そうすると以下のように表示されると思います。
上でも書いたように、
- 実行された関数名
- 呼び出し回数
- その関数自身の全体に対する処理時間の割合(%)、または実行時間(msec / usec)
- 実行するのにかかった総時間
が表示されます。
この時、関数名の左に色のついた丸があると思います。
調べた結果、丸の色の意味は以下のようです。
色 説明 青 PHPの標準関数 緑 自分で定義したクラスメソッド オレンジ 手続き型の関数 グレー include、またはrequire .phpファイルにかかる時間 処理の流れをわかりやすくグラフで表示することもできます。
100%でやった場合下の画像のように自分の環境ではとても膨大になりました。
これで、どこの処理で時間をかかっているのか把握しやすくなりました。
これを使いつつ、該当の関数の修正などを行っていけば、いい感じにチューニングできそうですね!参考にさせていただいた記事など
- 投稿日:2020-08-10T11:32:11+09:00
PHPのOOPについて自習したことをまとめてみる②(クラス定数について)
・宣言した定数は変更できません。
const1.php<?php class Goodbye { const LEAVING_MESSAGE = "Thank you for visiting W3Schools.com!"; } echo Goodbye::LEAVING_MESSAGE; ?>・クラス定数の使い方には、クラスの外部からアクセスする場合と、クラスの内部からアクセスする場合があります。
・クラス名、スコープ解決演算子(::)、定数名の順に使用することで、クラスの外部から定数にアクセスできます。
const2.php<?php class Goodbye { const LEAVING_MESSAGE = "Thank you for visiting W3Schools.com!"; public function byebye() { echo self::LEAVING_MESSAGE; } } $goodbye = new Goodbye(); $goodbye->byebye(); ?>. selfキーワードに続いてスコープ解決演算子(::)に続いて定数名を使用して、クラス内から定数にアクセスできます。
- 投稿日:2020-08-10T11:26:56+09:00
Factory MethodパターンのメリットとPHP実装例
Factory Method パターンとは
Factory Methodパターンは、インスタンスの生成をサブクラスにまかせることで、呼び出し元を生成されるインスタンスの具体的なクラスから解放することを目的としたデザインパターンです。文章ではよくわからなかったので、具体例を挙げます。
通常は次のようにインスタンスを生成します。
$product = new Product();これをインスタンス生成を担うメソッドに置き換えるのがFactoryMethodパターンです。
public function factoryMethod() : Product { return new Product(); }クラス図で表現すると次のようになります。
これはTemplateMethodパターンの応用だそうです。
具体的なクラス名による束縛から解放する
※次の例は自分で考えたものですので、間違っている箇所がありましたらご指摘いただけるとありがたいです
たとえば、スーパークラスに次のようなアルゴリズムがあったとします。
final public function write(string $message): Paper { $papper = new Paper(); $papper->written($message); return $papper; }紙を用意して、そこにメッセージを書き込んでいます。
ここで問題となるのは、紙以外のもの、たとえばホワイトボードなどにメッセージを書き込みたくなった場合です。
上記の例では
new Paper()
でクラス名を直接指定してインスタンスを生成しているため、このままでは紙以外のものにメッセージを書き込むことはできません。そこで、FactoryMethodパターンではインスタンス生成をサブクラスのメソッドに委ねます。まずはPaperを抽象化したWritableというスーパークラスを作成します。
abstract class Writable { public abstract function written(string $message): void; }次は、さきほどの
new Paper()
でインスタンスを生成していた箇所を、メソッドに置き換えます。abstract class Writer { final public function write(string $message): Writable { $writable = $this->createWritable(); $writable->written($message); return $writable; } protected abstract function createWritable(): Writable; }サブクラスは、次のように実装されます。
class PaperWriter extends Writer { protected function createWritable(): Writable { return new Paper(); } }class Paper extends Writable { private $message; public function written(string $message): void { $this->message = $message; echo '紙に' . $message . 'と書き込まれました' . "\n"; } public function show(): void { echo '紙には' . $this->message . 'と書かれています' . "\n"; } }このように、サブクラスに
createWritable()
実装を委ねることで、作者が自由に書き込むものを選ぶことができるようになります。これで、紙ではなくホワイトボードを使用したいという作者が現れても、write()
のアルゴリズムを修正する必要がなくなりました。ポイントは、Writerクラスの
write()
の中から具体的なクラス名が消えたということのようです。これを具体的なクラス名による束縛から解放されたと表現するようです。Factory Methodの実装方法
タイトルにあるFactory Methodとは、どのメソッドのことなのか、少し迷いました。
どうやら、インスタンスを生成するための抽象メソッドのことをFactory Methodと呼ぶようです。さきほどの例でいうとWriterクラスの
createWritable()
がFactory Methodにあたると思います。そして、おそらくWriterクラスのwrite()
はTemplate Methodだと思います。Factory Methodは、サブクラスで実装されることを期待していますが、次の3通りの記述方法があるようです。
- 抽象メソッドにする
- デフォルトの実装を用意しておく
- デフォルトでは例外を投げる
抽象メソッドにする
抽象メソッドにすることで、サブクラスはFactory Methodを実装しないと、エラーが発生するようになります。
protected abstract function createProduct(string $name): Product;デフォルトの実装を用意しておく
デフォルトの処理を実装しておくことで、サブクラスで実装されなかった場合もエラーは発生しなくなります。
protected function createProduct(string $name): Product { return new Product($name); }デフォルトでは例外を投げる
デフォルトで例外を投げる実装にしておくと、サブクラスでオーバライドしなかった場合に例外が教えてくれます。
protected function createProduct(string $name): Product { throw new FactoryMethodRuntimeException(); // 例外クラスは別途作成 }Factory Methodパターンのメリット
Factory Methodパターンを使うメリットをまとめてみます。
- 呼び出し元はオブジェクトの生成手順や分岐を気にする必要がなくなる
- 新しいオブジェクトを追加する場合も呼び出し元を修正する必要がない
自分の理解では、Factory Methodを実装すると必然的にTemplate Methodが作られるので、Factory MethodパターンはTemplate Methodパターンの応用といわれるのだと思います。
Factory MethodパターンPHP実装例
<?php abstract class Writable { public abstract function written(string $message): void; public abstract function show(): void; } abstract class Writer { final public function write(string $message): Writable { $writable = $this->createWritable(); $writable->written($message); return $writable; } protected abstract function createWritable(): Writable; } class Paper extends Writable { private $message; public function written(string $message): void { $this->message = $message; echo '紙に' . $message . 'と書き込まれました' . "\n"; } public function show(): void { echo '紙には' . $this->message . 'と書かれています' . "\n"; } } class PaperWriter extends Writer { protected function createWritable(): Writable { return new Paper(); } } function main(): void { $writer = new PaperWriter(); $writable1 = $writer->write('一昨日の記録'); $writable2 = $writer->write('昨日の記録'); $writable3 = $writer->write('今日の記録'); $writable1->show(); $writable2->show(); $writable3->show(); } main();
- 投稿日:2020-08-10T11:23:33+09:00
PHPのOOPについて自習したことをまとめてみる③(継承について)
extends1.php<?php class Fruit { public $name; public $color; public function __construct($name, $color) { $this->name = $name; $this->color = $color; } public function intro() { echo "The fruit is {$this->name} and the color is {$this->color}."; } } // Strawberry is inherited from Fruit class Strawberry extends Fruit { public function message() { echo "Am I a fruit or a berry? "; } } $strawberry = new Strawberry("Strawberry", "red"); $strawberry->message(); $strawberry->intro(); ?>extends2.php<?php class Fruit { public $name; public $color; public function __construct($name, $color) { $this->name = $name; $this->color = $color; } protected function intro() { echo "The fruit is {$this->name} and the color is {$this->color}."; } } class Strawberry extends Fruit { public function message() { echo "Am I a fruit or a berry? "; } } // Try to call all three methods from outside class $strawberry = new Strawberry("Strawberry", "red"); // OK. __construct() is public $strawberry->message(); // OK. message() is public $strawberry->intro(); // ERROR. intro() is protected ?>extends3.php<?php class Fruit { public $name; public $color; public function __construct($name, $color) { $this->name = $name; $this->color = $color; } protected function intro() { echo "The fruit is {$this->name} and the color is {$this->color}."; } } class Strawberry extends Fruit { public function message() { echo "Am I a fruit or a berry? "; // Call protected method from within derived class - OK $this -> intro(); } } $strawberry = new Strawberry("Strawberry", "red"); // OK. __construct() is public $strawberry->message(); // OK. message() is public and it calls intro() (which is protected) from within the derived class ?>'
- 投稿日:2020-08-10T11:12:33+09:00
PHPのOOPについて自習したことをまとめてみる③(アクセス修飾子について)
public, protected, privateについて
・public-プロパティまたはメソッドはどこからでもアクセスできます。これがデフォルトです
・protected -プロパティまたはメソッドは、クラス内およびそのクラスから派生したクラスによってアクセスできます
・private -プロパティまたはメソッドはクラス内でのみアクセスできます・アクセス修飾子は、プロパティとメソッドにつけることができる。
<プロパティにつけた場合>
accessproperty.php<?php class Fruit { public $name; protected $color; private $weight; } $mango = new Fruit(); $mango->name = 'Mango'; // OK $mango->color = 'Yellow'; // ERROR $mango->weight = '300'; // ERROR ?><メソッドにつけた場合>
accessmethod.php<?php class Fruit { public $name; public $color; public $weight; function set_name($n) { // a public function (default) $this->name = $n; } protected function set_color($n) { // a protected function $this->color = $n; } private function set_weight($n) { // a private function $this->weight = $n; } } $mango = new Fruit(); $mango->set_name('Mango'); // OK $mango->set_color('Yellow'); // ERROR $mango->set_weight('300'); // ERROR ?>
- 投稿日:2020-08-10T11:05:04+09:00
PHPのOOPについて自習したことをまとめてみる②(__construct, __destruct について)
__construct関数の使い方
construct.php<?php class Fruit { public $name; public $color; function __construct($name, $color) { $this->name = $name; $this->color = $color; } function get_name() { return $this->name; } function get_color() { return $this->color; } } $apple = new Fruit("Apple", "red"); echo $apple->get_name(); echo "<br>"; echo $apple->get_color(); ?>・set_〇〇関数が必要なくなるので、コード量が減る。
__destruct関数の使い方
destruct.php<?php class Fruit { public $name; public $color; function __construct($name, $color) { $this->name = $name; $this->color = $color; } function __destruct() { echo "The fruit is {$this->name} and the color is {$this->color}."; } } $apple = new Fruit("Apple", "red"); ?>・デストラクタは、オブジェクトが破壊されるか、スクリプトが停止または終了すると呼び出されます。
- 投稿日:2020-08-10T10:45:08+09:00
PHPのOOPについて自習したことをまとめてみる①(クラスの定義について)
参考にしたサイト
https://www.w3schools.com/php/php_oop_what_is.asp
クラスの定義
makeClass.php<?php class Fruit { // Properties public $name; public $color; // Methods function set_name($name) { $this->name = $name; } function get_name() { return $this->name; } } ?>・クラスとは→プロパティ(変数)やメソッド(関数)を集めたまとまりのこと。
クラスの定義2
makeClass.php<?php class Fruit { // Properties public $name; public $color; // Methods function set_name($name) { $this->name = $name; } function get_name() { return $this->name; } function set_color($color) { $this->color = $color; } function get_color() { return $this->color; } } $apple = new Fruit(); $apple->set_name('Apple'); $apple->set_color('Red'); echo "Name: " . $apple->get_name(); echo "<br>"; echo "Color: " . $apple->get_color(); ?>・メソッドを通じてプロパティにアクセスすると、こんな感じ。(getter, setterというんですかね)
- 投稿日:2020-08-10T06:40:08+09:00
$thisとは
- 投稿日:2020-08-10T05:07:02+09:00
Docker × Laravel Vueをインストールしてログイン機能まで実装する
DockerでLaravel環境を構築する記事を書きましたが、
Vue以外にもReactを選択したり、フロント側はそもそもリポジトリを分けて開発する場合もあり前回の記事では紹介しきれていませんでした。今回はLaravel環境を構築したあと、Vueのインストールまで試してみます。
Laravel UI とは
Laravelバージョン
5.8
までは、php artisan make:auth
というコマンドが用意されていましたが、Laravel 6.0
以降は Laravel UI パッケージに切り離されました。
- https://github.com/laravel/ui
- https://readouble.com/laravel/7.x/ja/frontend.html
- Laravel7はlaravel/uiは2.x系
- https://readouble.com/laravel/6.x/ja/frontend.html
- Laravel6はlaravel/uiは1.x系
前提
最強のLaravel開発環境をDockerを使って構築する【新編集版】
当記事は上記の記事の補足になる記事です。
Laravel環境構築
$ git clone git@github.com:ucan-lab/docker-laravel.git $ cd docker-laravel/infrastructure $ make create-project $ make install-recommend-packages # 推奨パッケージなのでやらなくても良いとりあえず、Laravelの環境を構築します。
環境
- PHP: 7.4.6
- Laravel: 7.24.0
- Laravel UI: 2.1.0
- Node: 14.2.0
- npm: 6.14.4
- yarn: 1.22.4
- Vue: 2.6.11
補足: npm, yarn どちらを使うのか?
正直どちらでも構わないと思います。
ただ、どちらを使うかプロジェクトで統一されている必要はあるかと思います。今回はnpm, yarnコマンドを併記する形で進めたいと思います。
Vue プリセットのインストール
公式の手順に沿って、実行します。
私のDocker環境の場合は下記の流れになります。$ cd infrastructure $ docker-compose exec app bash $ composer require laravel/ui $ php artisan ui vue --auth $ exit
app
コンテナを抜けてweb
コンテナに入ります。$ docker-compose exec web ash $ npm install # yarn $ npm run dev # yarn dev補足: スクリーンショット
ホーム画面
右上にLOGIN, REGISTERのメニューが追加されています。
登録画面
ログイン画面
ログイン後のホーム画面(ダッシュボード)
リセットパスワード画面
パスワードリセットのメールはMailHogで確認しています。
補足: webコンテナのNode.js
webコンテナ内にNode(npm, yarn)が入ってますが、コンテナ内でビルドするのは非常に時間がかかってしまうので実際の開発ではMacローカルにNodeを入れて実行させるのが良いです。
補足: Nodeバージョン固定化
特定のバージョンのNode.jsでしか動かして欲しくない場合、
package.json
のengines
フィールドにNode.jsのバージョンを明記しておくとyarn install
やnpm install
した時に警告を表示してくれます。package.json{ "private": true, "scripts": { "dev": "npm run development", "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "watch": "npm run development -- --watch", "watch-poll": "npm run watch -- --watch-poll", "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js", "prod": "npm run production", "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" }, "devDependencies": { "axios": "^0.19", "bootstrap": "^4.0.0", "cross-env": "^7.0", "jquery": "^3.2", "laravel-mix": "^5.0.1", "lodash": "^4.17.13", "popper.js": "^1.12", "resolve-url-loader": "^2.3.1", "sass": "^1.20.1", "sass-loader": "^8.0.0", "vue": "^2.5.17", "vue-template-compiler": "^2.6.10" }, "engines": { "node": "14.2.0" } }補足: Nodeバージョン自動切り替え設定ファイル
.node-version
をpackage.json
と同じディレクトリに配置しておくと、nodenv
が自動的にNodeのバージョンを切り替えてくれて便利です。.node-version14.2.0参考
- 投稿日:2020-08-10T01:56:28+09:00
シンプルなRESTFull API を PHPでちゃちゃっと作りたい場合
how to use
composer の インストール
$ docker pull composer $ alias composer='docker run --rm -it -v $PWD:/app composer'slim4-api-skeleton のインストールと設定
$ composer create-project maurobonfietti/slim4-api-skeleton [my-api-name] $ cd [my-api-name] $ composer install立ち上げと確認
$ docker-compose up -d --build $ curl http://localhost:8081/ {"api":"slim4-api-skeleton","version":"0.22.0","timestamp":1596992058}src/Controller/Home.php
<?php declare(strict_types=1); namespace App\Controller; use App\Helper\JsonResponse; use Pimple\Psr11\Container; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; final class Home { private const API_NAME = 'slim4-api-skeleton'; private const API_VERSION = '0.22.0'; private $container; public function __construct(Container $container) { $this->container = $container; } public function getHelp(Request $request, Response $response): Response { $message = [ 'api' => self::API_NAME, 'version' => self::API_VERSION, 'timestamp' => time(), ]; return JsonResponse::withJson($response, json_encode($message), 200); } public function getStatus(Request $request, Response $response): Response { $this->container->get('db'); $status = [ 'status' => [ 'database' => 'OK', ], 'api' => self::API_NAME, 'version' => self::API_VERSION, 'timestamp' => time(), ]; return JsonResponse::withJson($response, json_encode($status), 200); } }
- 投稿日:2020-08-10T00:28:28+09:00
【PHP】遅延静的束縛 static/self/parent
はじめに
PHP資格勉強中の備忘録として記事を書きます。
内容は「PHP5技術者認定上級試験問題集」に出てきたものをまとめています。
static
self
parent
が示すスコープで、どのクラスのメソッドが
呼び出されるのか本当にややこしかったのでまとめます。まとめ
スコープ self selfが記載されたクラス static 非転送コールのクラス parent parentが記載されたクラスの親クラス
- 転送コール
- self、static、parentによる静的メソッドのコール
- 非転送コール
- 明示的にクラス名を指定したもの(クラス名::メソッド名)
self
self
は「selfが記載されたクラス」がスコープとなる。
よって以下の実行結果は「foo」となる。class Foo { public static function who() { echo 'foo'; } //selfでwho()の呼び出し public static function test() { self::who(); } } class Bar extends Foo { public static function who() { echo 'bar'; } //Barクラスでtest()呼びだす Bar::test();static
static
は「非転送コールのクラス」がスコープとなる。
よって以下の実行結果は「bar」となる。
非転送コールである「Bar::test()」まで遡り、Barのメソッドが呼ばれる。class Foo { public static function who() { echo 'foo'; } //staticでwho()の呼び出し public static function test() { static::who(); } } class Bar extends Foo { public static function who() { echo 'bar'; } //Barクラスでtest()呼びだす Bar::test();parent
parent
は「parentが記載されたクラスの親クラス」がスコープとなる。
よって以下の実行結果は「foo」となる。class Foo { public static function who() { echo 'foo'; } } class Bar extends Foo { public static function who() { echo 'bar'; } //parentでwho()の呼び出し public static function test() { parent::who(); } //Barクラスでtest()呼びだす Bar::test();ついでに
class ParentClass { //クラス名を返す特殊な定数 public static function get_class_constant() { print __CLASS__; } //関数を呼び出したクラスのクラス名を返す関数 public static function get_class_function() { print get_called_class(); } class ChildClass extends ParentClass {} //子クラスから両方呼ぶ ChildClass::get_class_constant(); ChildClass::get_class_function();実行結果
ParentClass
ChildClassまとめ
class X { public static function foo() { //staticでwho()を呼ぶ //非転送コールを探す static::who(); } public static function who() { echo __CLASS__; } } class Y extends X { public static function test() { //それぞれでfoo()を呼び出す X::foo(); parent::foo(); self::foo(); } public static function who() { echo __CLASS__; } } class Z extends Y { public static function who() { echo __CLASS__; } } //Zでtest()を呼び出す Z::test();実行結果
X Z Z
最後に
なんともややこしい。
理解したはずなのに、記事書いてたときにごちゃごちゃしました。笑
間違っていたら教えて下さい。
- 投稿日:2020-08-10T00:17:26+09:00
【PHP8.0】PHP8.0の新機能
2020/08/04にPHP8.0がフィーチャーフリーズしました。
言語機能に関わるような機能の追加・変更が締め切られたということです。
今後はデバッグを繰り返しながら完成度を高めていき、2020/12/03にPHP8.0がリリースされる予定です。というわけでPHP8.0で対応することが決まったRFCを見てみましょう。
RFC
JIT
賛成50反対2で受理。
PHP8の目玉、JITです。PHPをネイティブコードにコンパイルし、さらにコンパイルした結果を次のリクエストに使い回すことができます。
平均的に1.3-1.5倍程度、さらにCPUバウンドな処理なら3倍以上という劇的な高速化が見込めます。PHP7.4で入ったプリローディングと組み合わせれば、これってもうコンパイル言語なのでは?
Named Arguments
賛成57反対18で受理。
名前付き引数です。// これまで array_fill(0, 100, 50); // 名前付き引数 array_fill(start_index: 0, num: 100, value: 50); // 同じ htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false); htmlspecialchars($string, double_encode: false);Pythonのやつとだいたい一緒です。
引数の多い関数で最後の引数だけ指定したいといった場合に有用です。
また、これまでは引数の順番を知らないとどの引数が何を表しているのかわかりませんでしたが、引数名で書くことで読みやすくなります。Match expression v2
賛成43反対2で受理。
match式です。echo match ("1") { true => 'Foo', 1 => 'Bar', "1" => 'Baz', }; // Baz厳密な比較、フォールスルーしない、返り値を持つ式である、といった具合に既存のswitch文の欠点のほとんどを解消したナイスな構文です。
逆に分岐内部には1式しか書けないため、分岐内部での複雑な処理はできません。
今後はswitchを使わざるを得ないところ以外はmatchで書くとよいでしょう。Nullsafe operator
賛成56反対2で受理。
ヌル安全オペレータです。$country = $session?->user?->getAddress()?->country;途中にnullが入ってくる可能性があるメソッドチェーンを簡単に書けるようになります。
?
に引っかかった時点で実行が中断され、それより先は処理されません。// bar()は実行されない null?->foo(bar())->baz();Union Types v2
賛成61反対5で受理。
UNION型のRFCです。function setNumber(int|float $number): void { // $numberはintかfloat }複数の型を受け取る、もしくは返すことができるようになります。
やりすぎると型システムの意味がなくなるので、程々に使っていくとよいでしょう。Mixed type v2
賛成50反対11で受理。
なんでもあり型です。class A { public function foo(mixed $value): mixed { return; // 必須 } }
var_dump()
の引数のように、あらゆる型を受け取りたい場合に使用する型です。
TypeScriptでいうところのanyです。
自発的に使用するのは基本的にやめておいたほうがよいでしょう。Attributes (v2)
賛成51反対1で受理。
アトリビュートです。
<<>>
でメタデータを埋め込むことができるようになります。<<WithoutArgument>> <<SingleArgument(0)>> <<FewArguments('Hello', 'World')>> function foo() {}これまではPHPDocやPSR-5のようにコメントで書くしかなく、強制力もありませんでした。
アトリビュートは、PHPの構文として実効力のある方法でメタデータを書けるようになります。Attribute Amendments
複数件の投票がありますが、全て受理されています。
アトリビュートの細かな追加仕様のRFCです。アトリビュートをカンマ区切りで複数書けるようにする、PhpAttributeだったクラス名をAttributeにする、などの仕様追加です。
アトリビュート自体の大きな変更はないようです。Shorter Attribute Syntax
アトリビュートの構文には幾つか問題があります。
<<Bar(2 * (3 + 3)>>Baz, (4 + 5) * 2)>> <<JoinTable( "User_Group", <<JoinColumn("User_id", "id")>>, <<JoinColumn("Group_id", "id")>>, )>>ネストしたりビット演算子と混ざった場合に非常にわかりにくい、あと今後ジェネリクスが追加されたときに更に混乱する、などです。
ということで代替構文が@@
・#[]
・<<>>
の間で争われ、投票でもRedditでも@@
が最多数でした。@@JoinTable( "User_Group", @@JoinColumn("User_id", "id"), @@JoinColumn("Group_id", "id"), )ただ決定はしたものの拒否反応を持つ人も多く、あと
@@
構文にも後から問題が見つかったみたいな話をしてるっぽいので、もしかしたら特例で変更があるかもしれません。Reclassifying engine warnings
賛成54反対3で受理。
警告レベルが厳しくなります。PHP7$a = 1; $a->b++;こんな訳のわからないコードでもPHP7ではE_WARNING止まりで動いていたのですが、今後はErrorExceptionになります。
あまり正しくない20以上の書式について、これまでE_NOTICEだった警告の一部がE_WARNINGに、E_WARNINGだった警告の一部が例外にと、厳しくなる方向に変更されます。Stricter type checks for arithmetic/bitwise operators
賛成57反対0で受理。
プリミティブでない値の算術演算のRFCです。var_dump( [] % [42] ); var_dump( new stdClass >> tmpfile() );全く意味の分からない演算ですが、PHP7ではとりあえず動きます。
が、こんなものが動いたところで役には立たないため、PHP8.0以降、配列・リソース・オブジェクトへの算術演算は全てTypeErrorになります。
オブジェクトの場合、演算子オーバーロードが設定されている一部のクラス(GMPなど)は引き続き演算が可能です。
またstringやintなどのプリミティブ型については、このRFCでの影響はありません。Consistent type errors for internal functions
賛成50反対2で受理。
内部関数の型引数についてのRFCです。function foo(int $bar) {} foo("not an int"); // TypeError strlen(new stdClass); // Warning: strlen() expects parameter 1 to be string, object given var_dump(DateTime::createFromFormat(new stdClass, "foobar")); // E_WARNING var_dump(DateTime::createFromFormat("foobar", "foobar", new stdClass)); // TypeError正しくない型の引数を渡した場合、ユーザ定義関数では常にTypeErrorが発生します。
内部関数の場合、TypeErrorだったりE_WARNINGだったりします。これはよくないのでTypeErrorに統一します。
従って、昔ながらのE_WARNINGを抑えるようなコードを書いていた場合はここで詰まります。
PHP本体のテストコードにも、この変更に引っかかるコード(わざとエラーを出すテスト)が1500ほどあるみたいです。
Saner string to number comparison
賛成44反対1で受理。
非厳密な比較演算子==
の挙動を変更するRFCです。"true" == 0; // true PHP7まで "true" == 0; // false PHP8数字と数値型でない文字列を比較する場合、これまでは文字列を数値に変換してから比較していましたが、今後は数値を文字列に変換してから比較するようになります。
数値と数値型文字列の比較や、数値以外の比較はこれまでと同じです。一見非常に危なそうな変更ですが、この変更で挙動がおかしくなる品質のプログラムは、まず間違いなくその前に
Reclassifying engine warnings
やConsistent type errors for internal functions
やStricter type checks for arithmetic/bitwise operators
あたりに引っかかって動かなくなるので、実害はほぼ無いと思います。Saner numeric strings
賛成30反対4で受理。
数値型文字列の定義を変更するRFCです。PHP7までは、数値型文字列の定義が複数ありました。
・正規の数値型文字列:0個以上のスペース+数値
・非正規の数値型文字列:正規の数値型文字列+任意の文字列
・それ以外は全て数値型文字列ではない。function foo(int $i) { var_dump($i); } foo("123"); // int(123) foo(" 123"); // int(123) foo("123 "); // int(123) with E_NOTICE "A non well formed numeric value encountered" foo("123abc"); // int(123) with E_NOTICE "A non well formed numeric value encountered" foo("string"); // TypeErrorこのせいで色々と面倒なことになっていました。
特に123
と123
が異なるのはとても違和感がありますね。ということで、PHP8では数値型文字列の定義をひとつにします。
・数値型文字列:0個以上のスペース+数値+0個以上のスペース
・それ以外は全て数値型文字列ではない。これにより
123
と123
は緩やかに同じ値になります。
そして、123abc
は数値型文字列ではなくなります。function foo(int $i) { var_dump($i); } foo("123"); // int(123) foo(" 123"); // int(123) foo("123 "); // int(123) foo("123abc"); // TypeError foo("string"); // TypeErrorぶっちゃけ
Saner string to number comparison
なんかより、こちらのほうがよっぽど影響範囲が大きいです。
ただ数値型文字列ではなくなったとはいえ、(int)"123abc"
が0
になるとあまりにも被害甚大すぎるので、さすがにこれは123
にしてくれるようです。Treat namespaced names as single token
賛成38反対4で受理。
namespaceのパーサトークンを変更するRFCです。これまで名前空間
Foo\Bar
は字句解析でT_STRING,T_NS_SEPARATOR,T_STRING
と分解されていましたが、今後はT_NAME_QUALIFIED
ひとつになります。PHPを使う側としては、token_get_allを使っているような変態スクリプト以外は全く無関係です。
と思いきやしれっと、名前空間の空白が禁止されるとか書いてありました。
ちなみに違反するスクリプトは、Composer上位2000パッケージ中僅か5か所だけでです。Allow trailing comma in parameter list
賛成58反対1で受理。
関数の引数の末尾カンマのRFCです。PHPでは配列の末尾カンマは昔から使えていて、その後PHP7.3で関数呼び出しおよびほとんどのリストでも末尾カンマが使えるようになりました。
関数呼び出し側は対応したのに、関数の引数のほうは何故か対応していなかったので、その対応の追加です。
function hoge( $foo, $bar, // PHP8.0以降OK ){} hoge(1, 2, ); // PHP7.3以降OKAllow trailing comma in closure use lists
賛成49反対0で受理。
use内の末尾カンマのRFCです。$longArgs_longVars = function ( $longArgument, $longerArgument, $muchLongerArgument, // ↑の引数末尾カンマでOKになった ) use ( $longVar1, $longerVar2, $muchLongerVar3, // このRFC ) { };
use
の末尾にもカンマを置くことができなかったので、その対応の追加です。
この抜けの補完によって、おそらく全ての列挙箇所で末尾カンマを使えるようになったのではないでしょうか。Ensure correct signatures of magic methods
賛成45反対2で受理。
マジックメソッドの引数のRFCです。マジックメソッドには、これまでは正しくない型を書くことが可能でした。
PHP7までOKclass Foo { public function __get(array $name): void{} }今後はこれが禁止され、正しい型しか書けないようになります。
もしくは、単に何も書かないかです。PHP8.0class Foo { public function __get(string $name): mixed{} }例によってNikitaがComposer上位1000パッケージを調べたところ、違反するスクリプトは僅か7か所だけでした。
Configurable string length in getTraceAsString()
賛成36反対2で受理。
例外の文字列展開の文字数を変更するRFCです。
Throwable::getTraceAsString()
などでエラーを文字列展開する際、スタックトレース中の文字列は15バイトで打ち切られてしまいます。
そのため、深く調査したい場合などに難しい状態でした。そこでini設定
zend.exception_string_param_max_len
を追加し、設定されたバイト数をスタックトレースに展開できるようにします。
デフォルトは既存の15です。Remove inappropriate inheritance signature checks on private methods
賛成24反対11で受理。
privateメソッドの継承に関するRFCです。class A { final private function finalPrivate() { echo __METHOD__ . PHP_EOL; } } class B extends A { private function finalPrivate() { echo __METHOD__ . PHP_EOL; } }privateメソッドは継承されないにも関わらず、この書き方は
Cannot override final method
のFatal Errorになります。
その他幾つかの場合において、継承時にprivateメソッドのオーバーライドチェックが行われることがあります。従って、PHP8.0以降privateメソッドは継承時にオーバーライドチェックを行わないようにします。
Abstract trait method validation
賛成52反対0で受理。
トレイトのabstractメソッドの挙動に関するRFCです。トレイトにabstractメソッドを書いた場合、継承時のルールが何かおかしくなっています。
PHP7までOKtrait MyTrait { abstract public function publicFunction(): string; abstract private function privateFunction(): string; // cannot be declared private } class TraitUser { use MyTrait; // 何故かOK public function publicFunction(): stdClass { } }何故かシグネチャが異なっていても通ってしまうので、これを通常のクラス継承と同じように修正します。
またついでにabstract privateメソッドを定義できるようにします。PHP8.0trait MyTrait { abstract private function neededByTheTrait(): string; public function doSomething() { return strlen($this->neededByTheTrait()); } } class TraitUser { use MyTrait; // OK private function neededByTheTrait(): string { } // 返り値の型が異なるのでNG private function neededByTheTrait(): stdClass { } // staticとnonstaticなのでNG private static function neededByTheTrait(): string { } }traitのabstract privateメソッドは、useしたクラスで実装が必須となります。
privateメソッドの実装を強制させるような状況って、あまり考えたくありませんが。Make sorting stable
賛成24反対11で受理。
ソートを安定ソートにするRFCです。$array = [ 'c' => 1, 'd' => 1, 'a' => 0, 'b' => 0, ]; asort($array);PHPのソートはこれまで不安定ソートだったので、ソート後に
a
とb
の順番、c
とd
の順番が保証されませんでした。
PHP8.0以降はソート後の順番が['a' => 0, 'b' => 0, 'c' => 1, 'd' => 1]
で保証されます。Constructor Property Promotion
賛成46反対10で受理。
オブジェクト初期化子です。class Point { // プロパティx,y,zが生える public function __construct( public float $x = 0.0, public float $y = 0.0, public float $z = 0.0, ) {} }コンストラクタにかぎり、引数に可視性を指定すると自動的にプロパティとして登録してくれます。
これまではオブジェクトの初期化に多数の定型文が必要でしたが、今後は楽に書けるようになります。Always available JSON extension
賛成56反対0で受理。
JSONを常時有効にするRFCです。実はこれまでJSONエクステンションを
--disable-json
オプションでインストールしないようにできていたのですが、これができなくなります。マニュアルにも書かれていないほどの隠し機能なので、無くなっても全く問題ないでしょう。
Unbundle ext/xmlrpc
賛成50反対0で受理。
ext/xmlrpcを外すRFCです。xmlrpcは内部的にxmlrpc-epiを使っていますが、こちらが既に放棄されているためサポートから外します。
これによってxmlrpc_xxx関数が使えなくなります。元より実験的なものである警告がなされていたモジュールなので、これが影響する人はほとんどいないでしょう。
Non-capturing catches
賛成48反対1で受理。
例外を受け取らないRFCです。try { changeImportantData(); } catch (PermissionException) { echo "You don't have permission to do this"; }catchした例外を使用しない場合は、最初から受け取らないようにすることができます。
決して例外の握り潰しに使うための機能ではありません。Locale-independent float to string cast
賛成42反対1で受理。
一部言語の(string)キャストに関するRFCです。フランス語では小数点が
,
です。
このためロケールをフランス語に設定した場合、(string)3.14
は"3,14"
になります。setlocale(LC_ALL, "de_DE"); $f = 3.14; // float(3,14) $s = (string) $f; // string(4) "3,14" $f = (float) $s; // float(3)しかし
"3,14"
を小数に戻すことはできず3
になります。
この矛盾を解消するため、またそもそも文字列表現が異なる値になる時点でわかりにくいので、stringキャストはロケールに関わらず常に"3.14"
と変換することにします。英語・日本語など小数点が
.
のロケールには全く影響ありません。Change default PDO error mode
賛成49反対2で受理。
PDOのデフォルトエラーモードのRFCです。PHP7までPDO::ATTR_ERRMODEのデフォルトは
PDO::ERRMODE_SILENT
でした。
これはエラーが出ても何も言わないので非常によろしくない設定です。PHP8.0からは
PDO::ERRMODE_EXCEPTION
がデフォルトになります。
めでたし。Add str_starts_with and str_ends_with to PHP
賛成51反対4で受理。
StartsWith/EndsWithです。echo str_starts_with ( 'abcdef' , 'abc' ); echo str_ends_with ( 'abcdef' , 'def' );ユーザランドで容易く実装できそうに見えますが、実はこの関数の実装には罠が多く、下手に書くとすぐに非効率になります。
言語側で用意してくれるならそれにこしたことはないでしょう。str_contains
賛成43反対9で受理。
str_containsです。str_contains('放課後アトリエといろ', '放課後'); // true何故かこれまでずっと存在せず、
strpos($haystack, $needle)!==false
という謎の書式を強いられていた『〇〇を含む』が、ようやく簡潔に書けるようになります。str_containsとstr_starts_with/str_ends_withが実装されたことにより、PHPのテキスト処理に対する関数は出揃ったと言えそうです。
throw expression
賛成46反対3で受理。
throw文がthrow式になります。// PHP7 if(!$nullableValue){ throw new InvalidArgumentException(); } $value = $nullableValue; // 概ね同じ $value = $nullableValue ?? throw new InvalidArgumentException();throwはこれまで文だったので、式の間に含めることができませんでした。
今後はもっと気軽にthrowしていくことができるようになります。Object-based token_get_all() alternative
賛成47反対0で受理。
token_get_allの代替構文のRFCです。token_get_all関数は、パースした結果を配列で返してきていました。
このRFCでは新たに
PhpToken
クラスを導入し、パースした結果をオブジェクトで受け取るようにします。
これによってコードがわかりやすくなり、メモリ消費量も減少します。互換性のため、既存のtoken_get_allはそのままです。
Stringable
賛成29反対9で受理。
StringableインターフェイスのRFCです。Countableインターフェイスを実装するとcountできるようになり、Traversableインターフェイスを実装するとforeachできるようになります。
同様に__toStringできるようになるインターフェイスStringableを実装しようという提案です。
interface Stringable { public function __toString(): string; }これを強制するとありとあらゆる実装がぶち壊れるため、
__toString
を実装しているクラスは暗黙的にStringable
をimplementsします。
従って、使用する側としては気にする必要はありません。どちらかというとライブラリで
string|Stringable
を受け取りたいときのためのマーカーインターフェースのようなものです。
実際のユースケースはSymfonyで見ることができます。Allow ::class on objects
賛成60反対0で受理。
クラス名を取得するRFCです。
stdClass::class
でクラス名"stdClass"
を取得できますが、いったんオブジェクト化してしまうと何故か::class
できません。
get_classを使う必要があります。PHP8では
$object::class
と書けるようになります。echo stdClass::class; // class echo (new stdClass)::class; // PHP8.0以降OKStatic return type
賛成54反対0で受理。
返り値にstaticを書けるようにするRFCです。PHP7では、返り値として
parent
やself
を書くことが可能です。<?php class FUGA{} class HOGE extends FUGA{ public function a(): parent{ return new parent(); } } (new HOGE())->a(); // FUGAになる同様に
static
も書けるようにします。`
これは遅延静的束縛のほうのstaticです。// PHP7までsyntax error class Test { public function createFromWhatever($whatever): static { return new static($whatever); } }個人的には静的遅延束縛を全く使わないので、何が嬉しいのかよくわかりません。
Variable Syntax Tweaks
賛成47反対0で受理。
変数構文のRFCです。PHP7.0において変数構文の大規模なリファインがありましたが、そのときに見過ごされていた一部の構文についての対応です。
とあるのですが、見過ごされていたくらいなのですごい細かくて気付かないくらいの変更です。
また、これまではシンタックスエラーだった構文が有効になるという変更なので、互換性のない変更はありません。$bar = "bar"; echo "foobar"[0]; // OK echo "foo$bar"[0]; // PHP7までsyntax error、8からOK echo __FILE__[0]; // PHP7までsyntax error、8からOKDOM Living Standard API
賛成37反対0で受理。
DOMの更新についてのRFCです。PHPのDOMは、かなり昔にW3 Groupが作ったDOM Level 3に準拠して作られています。
その後DOMはLiving Standardとなり、WHATWGが管理するようになりました。
当時よりだいぶ改善されているので、その変更を取り込みます。まあでもDOM面倒臭いからあんまり使わないよね。
Always generate fatal error for incompatible method signatures
賛成39反対3で受理。
メソッドシグネチャのRFCです。継承時にLSP原則に違反する書き方をすると、PHP7では致命的エラーもしくはE_WARININGのどちらかになります。
// こっちは致命的エラー interface I { public function method(array $a); } class C implements I { public function method(int $a) {} } // こっちはE_WARNING class C1 { public function method(array $a) {} } class C2 extends C1 { public function method(int $a) {} }これを全ての場合において致命的エラーに統一します。
むしろ何故いままで致命的エラーになっていなかったんだ案件。
Arrays starting with a negative index
賛成17反対2で受理。
マイナススタートの配列インデックスが使えるようになります。$arr = [ -10 => 'a' ]; $arr[] = 'b'; // PHP7.4まで0、PHP8から-9いったい誰が得するのかよくわかりません。
Weak maps
賛成25反対0で受理。
弱いマップです。PHP7.4で弱い参照が導入されましたが、現実的には弱いマップのほうが使われてるよ、ってことで導入されるようです。
私には、普通のマップではなくこっちを使う場面が思いつかない。
get_debug_type
賛成42反対3で受理。
get_debug_typeです。一言で言うとgettype + get_classです。
オブジェクトであればクラス名を、プリミティブ型であれば型名を取得できます。gettype(1); // integer get_class(1); // TypeError get_debug_type(1); // int gettype(new stdClass); // object get_class(new stdClass); // stdClass get_debug_type(new stdClass); // stdClassいちいち使い分ける必要がなく、また
integer
とかいう正しくない型名が入ってきたりしないので便利です。Don't automatically unserialize Phar metadata outside getMetadata()
賛成25反対0で受理。
Pharメタデータの処理方法に関するRFCです。PHP7では、
file_exists("phar://path/to/phar.ext")
のようにPharファイルを探すだけで、自動的にそのメタデータが展開されてしまいます。
これによって、いわゆる安全でないデシリアライゼーション攻撃が可能になります。そこで、Phar::getMetadataを手動で呼び出さない限り、メタデータを展開しないように修正します。
またgetMetadataはメタデータの展開を
unserialize
で行っています。
そこで引数$unserialize_options
を追加し、デシリアライズする方法を任意に変更できるようにします。でも、Cookieなど外部で汚染できるデータではなく、サーバに置いてあるPharファイルが汚染されている時点でアウトな気がしてならないんだけどどうなのでしょうかね。
Add support for CMS
投票なし。
CMSサポートのRFCです。CMSといってもContents Management SystemではなくRFC5652のCryptographic Message Syntaxです。
openssl_pkcs7_encryptに相当するopenssl_cms_encryptなどの暗号化・復号する関数が実装されます。RFCを立てはしたものの、OpenSSLの基礎機能だし投票いらんじゃろということで投票なしに採用されたようです。
感想
おい誰だよPHP8たいしたことないんじゃねえのとか言った奴は。
相当にすごいことになっています。アトリビュートのあたりは少々見通しが不透明ですが、それ以外は順当に実装されることでしょう。
これらを使うことで、PHP8では書きやすく、読みやすく、そして高速なプログラムを作ることができるようになります。
そのぶんだけ、これまで適当な書き方をしていたコードは移植がたいへんになりそうですね。なお、このリストは、RFCが立って投票が受理され、導入されることが決定したものだけの一覧です。
これ以外にもバグ修正や、RFCを立てるほどでもないちょっとした変更なども多々入っているはずです。
PHP8によって、PHPはかなり完成に近づいてきた感があります。
これ以上に欲しい大きなものってジェネリクスくらいじゃね?…なんて言ってたら次回もまた色々突っ込まれてきそうですけどね。
先日Microsoftが企業としてPHP8をサポートしないという残念なお知らせがありましたが、今のところは有志によってWindows版ビルドも迅速に提供されています。
PHP8.0ではさしたる影響はないでしょう。
まあ今後どうなるかはわかりませんし(Windows版固有バグなど)、とっととWSL2に移行しろって話なのかもしれませんが。
でもローカルなんかXAMPPで十分なんだよめんどくせーコマンドなんていちいち打ちたくねーんだよもっと手抜きさせろ。