20200810のPHPに関する記事は19件です。

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'
            )
        );

以上

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

ログイン機能と投稿機能の作成(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

主な機能

ログイン機能

・サインアップ画面
スクリーンショット 2020-08-10 17.40.15.png
・サインイン画面
スクリーンショット 2020-08-10 17.40.23.png

投稿機能

左のレイヤーで全ユーザーの投稿を表示するか、自分の投稿のみを表示するか切り替えられる。
真ん中のレイヤーでは、ユーザー名と投稿が表示される。
右のレイヤーではつぶやきを投稿できる。
画面サイズが小さくなると右のレイヤーが下に回り込む。
スクリーンショット 2020-08-10 17.50.11.png

開発手順

  1. 要件定義
  2. 環境設定
  3. データベース設計
  4. コーディング
  5. xserverデプロイ

1.用件定義

今回作成するアプリに必要な機能
・サインアップ
・サインイン
  ・セッション
・つぶやき投稿

--余裕があれば--
・検索機能

phpとDBを用いた構築を行う。
「udemy」で勉強したBootstrapも活用する。

2.環境選定

基本的には「Udemy」で習った環境を使う。(上記スペックに記載)
本番環境では知名度の高い「xserver」を使用する。
GitとGitHubは練習として使っていく。

3.データベース設計

正規化を意識して設計。調べたところTwitterのDBは検索性を高めるために投稿された月ごとにインデックすしてるようです。(今回はスルー)
スクリーンショット 2020-08-10 20.51.58.png

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の違いや使い方について調べる

参考文献

PHP+MySQLでポートフォリオ作成

GitHubアカウント

freedog1
https://github.com/freedog1/tsubuyaki_1

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

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) ⇨ 準備中

※ 連載で使うサンプルコード

Sample Code Link on Github

● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。

はじめに

今回は概論と本人の持論が主な話になります。メモリーとGCに対する知識を既にお持ちの方は、すぐに次の話から読んで頂いても良いと思います。2話からは、PHPの仕様に基づいた客観的な内容が主になります。

1. GCは何をするの?

image.png

GCとは、一言で「メモリー内のゴミ自動回収仕組み」と言えます。それを意識しながら内容をご覧いただけると良いと思います。

この概念は、日常生活のゴミ捨てとかなり似ています。
- 呼ばなくても、回収に来てくれる。(自動回収)
- 毎週、指定日に回収に来てくれる。(GC発動条件)

もし、上記の仕組みがなかったら、私達はゴミ処理において相当苦しみを感じたと思います。

GCは、プログラムが使うメモリー空間において、ほぼ似たような役目を遂行します。

プログラム内のメモリー空間内のゴミ状態の参照や変数を、特定条件が達したタイミングで自動でメモリーから解除し、メモリー空間を広く確保してくれます。「エンジニアが明示的に解除しなくても」やってくれるのが核心です。

2. なぜGCを知る必要があるの?

1) PHPにおいてのGCは重要?

実のところPHPは、JAVA・TOMCAT基盤などのシ資源共有型のマルチスレッドステムとは違い、一つのリクエストに対して単体プロセスとして実行させ終了する形で使われる場合が多いです。
こういう単体プロセスで処理されるタイプの場合は、プロセス終了とともにすべてのメモリー専有が解除されるため、Memory LeakとGCに対するトラブルで大きい問題になる場合は少ないです。

しかし最近のPHPは、
- パッケージやフレームワークをベースとした開発によりプログラムのサイズは増大
- バッチ系やデーモン系の大容量処理プログラムなどのニーズも多くなったこと

により、PHPにおいてのメモリー管理の関心は高くなっていると思うので、軽く知っておいても良いと思っています。

2) GCは多くの言語においての「メモリー管理メカニズム」

image.png

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.php

4) 連載に使うサンプルコード

Sample Code Link on Github

● 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からはデフォルトで推奨されています。近いうちには、この内容が大きく変わるかもしれません。

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

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) ⇨ 準備中

※ 連載で使うサンプルコード

Sample Code Link on Github

● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。

はじめに

今回は概論と本人の持論が主な話になります。メモリとGCに対する知識を既にお持ちの方は、すぐに次の話から読んで頂いても良いと思います。2話からは、PHPの仕様に基づいた客観的な内容が主になります。

1. GCは何をするの?

image.png

GCとは、一言で「メモリ内のゴミ自動回収仕組み」と言えます。それを意識しながら内容をご覧いただけると良いと思います。

この概念は、日常生活のゴミ捨てとかなり似ています。
- 呼ばなくても、回収に来てくれる。(自動回収)
- 毎週、指定日に回収に来てくれる。(GC発動条件)

もし、上記の仕組みがなかったら、私達はゴミ処理において相当苦しみを感じたと思います。

GCは、プログラムが使うメモリ空間において、ほぼ似たような役目を遂行します。

プログラム内のメモリ空間内のゴミ状態の参照や変数を、特定条件が達したタイミングで自動でメモリから解除し、メモリ空間を広く確保してくれます。「エンジニアが明示的に解除しなくても」やってくれるのが核心です。

2. なぜGCを知る必要があるの?

1) PHPにおいてのGCは重要?

実のところPHPは、JAVA・TOMCAT基盤などのシ資源共有型のマルチスレッドステムとは違い、一つのリクエストに対して単体プロセスとして実行させ終了する形で使われる場合が多いです。
こういう単体プロセスで処理されるタイプの場合は、プロセス終了とともにすべてのメモリ専有が解除されるため、Memory LeakとGCに対するトラブルで大きい問題になる場合は少ないです。

しかし最近のPHPは、
- パッケージやフレームワークをベースとした開発によりプログラムのサイズは増大
- バッチ系やデーモン系の大容量処理プログラムなどのニーズも多くなったこと

により、PHPにおいてのメモリ管理の関心は高くなっていると思うので、軽く知っておいても良いと思っています。

2) GCは多くの言語においての「メモリ管理メカニズム」

image.png

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.php

4) 連載に使うサンプルコード

Sample Code Link on Github

● 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からはデフォルトで推奨されています。近いうちには、この内容が大きく変わるかもしれません。

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

Laravelのインストール、初期設定、GitHubの使用

Laravelのインストール

Laravelのインストールには専用インストーラを使用する方法とComposerを利用する方法の2つがありますが、今回はComposerでのインストールを行います。
DBはMySQLを使います。
PHPとphpmyadminが使える環境を前提に進めます。

Composerのインストール

ComposerはPHPのパッケージ管理ツールです。
下記のサイトからインストールできます。
Composer

Composerのインストール確認

下記のコマンドを実行してバージョンが表示されればインストールできています。

terminal
 composer --version
 //composer -vでも可

Composerのパッケージインストール時間の短縮

packagist.jpの使用

海外に向いているリポジトリを、日本の有志の方が運用をおこなっているリポジトリ( https://packagist.jp/ )に向ける事でインストールにかかる時間が短縮できます。

リポジトリの追加
terminal
composer config -g repos.packagist composer https://packagist.jp
composer update
リポジトリの削除
terminal
composer config -g --unset repos.packagist

Prestissimoの使用

Prestissimoを導入してダウンロードを並列処理する
Prestissimo

terminal
composer global require hirak/prestissimo

Composerを使ったLaravelのインストール

terminal
  //最新版をインストールする場合
  composer create-project laravel/laravel [プロジェクト名] --prefer-dist

  //バージョンを指定してインストールする場合(x.0の部分がバージョン指定)
  composer create-project laravel/laravel [プロジェクト名] --prefer-dist "x.0.*"

※ --prefer-distはファイル圧縮版を使用するというオプションです。

Laravelの動作確認(簡易サーバー)

プロジェクトのルートに移動して下記コマンドを実行します。

terminal
 php artisan serve

http://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',            
]

デバッグバーの使用

インストール

簡易サーバーを立ち上げると下部にデバッグ情報が表示されるようになります。

terminal
composer require barryvdh/laravel-debugbar

非表示

本番環境など表示したくない環境では下記のように値を変更します。

.env
APP_DEBUG=true
//↓
APP_DEBUG=false

DB(MySQL)の設定

phpMyAdminを使ってDB(DB名)とユーザー(ユーザー名、パスワード)を作成して、下記を編集します。

.env
 DB_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 --auth

npmでのパッケージのインストール

Please run "npm install && npm run dev" to compile your fresh scaffolding.と表示されるので従います。

npmがインストールされていない場合、Node.jsをインストールします。
(Node.jsがインストールされていればnpmも使えるようになります。)
Node.js

Node.js、npmのインストール確認

バージョンが表示されればインストールできています。

terminal
node -v
npm –v

npmによるパッケージインストール

package.jsonの内容に従ってパッケージがインストールされます。

terminal
npm ci

CSSと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つのテーブルが作成されます。)

terminal
php artisan migrate

作成されるテーブル

  • failed_jobs
  • migrations
  • password_resets
  • (users)

Laravelの動作確認(簡易サーバー)

プロジェクトのルートに移動して下記コマンドを実行します。

terminal
 php artisan serve

http://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のリポジトリを作成します。

terminal
git init

.gitignore

Laravelでは、gitを使う前提で.gitignoreがディレクトリ直下に用意されています。

無視ファイル/フォルダ(一部抜粋) 内容
.env 接続情報などが保存されているので、誤ってGitHubに上がらないように設定されている
/vender composerでインストールしたファイル (laravel自体もここに入っている)
/node_modules npmでインストールしたファイルが入っている

push

terminal
git add .
git commit -m "メッセージ"
git remote add origin https://github.com/ユーザー名/リポジトリ名.git
git push -u origin master

GitHubからcloneする

今度はGitHubからファイルを取ってきて使えるようにしていきます。

terminal
git clone https://github.com/ユーザー名/リポジトリ名.git

cloneしただけでは、Laravelの動作に必要なファイルが足りないので、.gitignoreで無視していたファイルを作成します。

.env

DBの接続情報

.env.exampleをコピーして.envにリネームして、接続情報などを記入します。
DBがなければ作成が必要になります。

デバッグバーの使用

使用する場合のみ変更します。

APP_DEBUG=false
//↓
APP_DEBUG=true
アプリケーションキーの作成
terminal
php artisan key:generate

.envのAPP_KEYに値が書き込まれます。

/vender配下のファイル (Composerでのパッケージインストール)

cloneした環境にComposerがインストールがされていない場合はインストールします。
下記コマンドでcomposer.jsonの内容に従ってパッケージがインストールされます。

terminal
composer install

/node_modules配下のファイル (npmでのパッケージインストール)

cloneした環境にnpmがインストールがされていない場合はインストールします。
下記コマンドでpackage.jsonの内容に従ってパッケージがインストールされます。

terminal
npm ci

migration

テーブルを作成します。

terminal
php artisan migrate

Laravelの動作確認(簡易サーバー)

プロジェクトのルートに移動して下記コマンドを実行します。

terminal
 php artisan serve

設定変更が反映されない場合

キャッシュやコンフィグのクリアを試してみてください。

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

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){
 ...
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.ymlDockerfile を変更する場合は予めコンテナを破棄しておくと良いです。

docker-compose.yml
services:
  mail:
    image: mailhog/mailhog
    ports:
      - 8025:8025

mail サービスの設定を 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 

mail のコンテナが起動していればokです。

backend/.env

.env
MAIL_HOST=mail
MAIL_PORT=1025
MAIL_FROM_ADDRESS=info@example.com

環境変数にMailHogの設定を追記します。
ホスト名には mail サービス名を設定、MailHogのSMTPは1025ポートがデフォルトで待ち受けています。
それと送信元アドレスの設定が必要です。

メール送信テスト

$ docker-compose exec app php artisan tinker
Mail::raw('test mail',function($message){$message->to('test@example.com')->subject('test');});

http://127.0.0.1:8025

ScreenShot 2020-08-10 2.04.49.png

ScreenShot 2020-08-10 2.04.53.png

参考

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

PHPパフォーマンスチューニング

夏休み記事2件目です。

PHPのパフォーマンスチューニングする場合、どの部分がボトルネックになっているか把握しないといけません。
今回は、Xdebugとwebgrindを使ったプロファイリングをやってみたのでまとめます。
なお、本記事の画像は、プログラムの情報が漏れないようぼかしています。

とりあえずどのように見えるのか紹介

Xdebugでphpのプロファイリングをしてそのアウトプットをwebgrindで表示すると以下のような感じになります。
スクリーンショット 2020-08-10 13.31.19.png

  • 実行された関数名
  • 呼び出し回数
  • その関数自身の全体に対する処理時間の割合(%)、または実行時間(msec / usec)
  • 実行するのにかかった総時間

が表示されます。
処理の流れをわかりやすくグラフで表示することもできます。
スクリーンショット 2020-08-10 13.35.51.png

それでは、使用するための準備をしていきます。
なお、本記事の実行環境は、以下の通りです。

  • プロファイリングしたい対象サーバ(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の場所がわからない場合は、以下のものを実行するとわかります。

bash
php --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を使用してみます。

bash
docker run --rm -v /(ここにプロファイル結果が保存されているパスを指定):/tmp -p 80:80 jokkedk/webgrind:latest

見やすく表示してみる

これでwebgrindは使えます。
プロファイリング対象と同じサーバーで動かしたい場合はここを見てやってみてください。

それでは、http://localhost:80/index.phpにアクセスすると、以下のような画面が出てくると思います。

スクリーンショット 2020-08-10 15.52.14.png

右上の機能の説明は左から、

  • show:(処理時間がかかった順に)どれほど表示するか(100%にすると、全ての処理が表示されます。)
  • Auto(newest):表示させるプロファイリング結果、デフォルトは、一番新しいファイル
  • percent:表示単位を(percent / milliseconds / microseconds)から選択

これでupdateを押すと、設定が反映されます。
そうすると以下のように表示されると思います。
スクリーンショット 2020-08-10 13.31.19.png
上でも書いたように、

  • 実行された関数名
  • 呼び出し回数
  • その関数自身の全体に対する処理時間の割合(%)、または実行時間(msec / usec)
  • 実行するのにかかった総時間

が表示されます。
この時、関数名の左に色のついた丸があると思います。
調べた結果、丸の色の意味は以下のようです。

説明
PHPの標準関数
自分で定義したクラスメソッド
オレンジ 手続き型の関数
グレー include、またはrequire .phpファイルにかかる時間

処理の流れをわかりやすくグラフで表示することもできます。
100%でやった場合下の画像のように自分の環境ではとても膨大になりました。
スクリーンショット 2020-08-10 13.30.54.png

これで、どこの処理で時間をかかっているのか把握しやすくなりました。
これを使いつつ、該当の関数の修正などを行っていけば、いい感じにチューニングできそうですね!

参考にさせていただいた記事など

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

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キーワードに続いてスコープ解決演算子(::)に続いて定数名を使用して、クラス内から定数にアクセスできます。

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

Factory MethodパターンのメリットとPHP実装例

Factory Method パターンとは

Factory Methodパターンは、インスタンスの生成をサブクラスにまかせることで、呼び出し元を生成されるインスタンスの具体的なクラスから解放することを目的としたデザインパターンです。文章ではよくわからなかったので、具体例を挙げます。

通常は次のようにインスタンスを生成します。

$product = new Product();

これをインスタンス生成を担うメソッドに置き換えるのがFactoryMethodパターンです。

public function factoryMethod() : Product
{
  return new Product();
}

クラス図で表現すると次のようになります。

スクリーンショット 2020-08-10 11.26.10.png

これは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();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
?>'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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");
?>

・デストラクタは、オブジェクトが破壊されるか、スクリプトが停止または終了すると呼び出されます。

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

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というんですかね)

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

$thisとは

$thisとは

PHPでスコープ外で定義された変数を利用するための擬似変数

thisがある時とない時の違い

class numbers{

  public $number = 'one';

  public function saynumber(){
    $number = 'two';

    echo $number;//出力結果はtwo
    echo $this->number;//出力結果はone
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker × Laravel Vueをインストールしてログイン機能まで実装する

DockerでLaravel環境を構築する記事を書きましたが、
Vue以外にもReactを選択したり、フロント側はそもそもリポジトリを分けて開発する場合もあり前回の記事では紹介しきれていませんでした。

今回はLaravel環境を構築したあと、Vueのインストールまで試してみます。

Laravel UI とは

Laravelバージョン 5.8 までは、php artisan make:auth というコマンドが用意されていましたが、 Laravel 6.0 以降は Laravel UI パッケージに切り離されました。

前提

最強のLaravel開発環境をDockerを使って構築する【新編集版】

当記事は上記の記事の補足になる記事です。

Laravel環境構築

$ git clone git@github.com:ucan-lab/docker-laravel.git
$ cd docker-laravel/infrastructure
$ make create-project
$ make install-recommend-packages # 推奨パッケージなのでやらなくても良い

http://127.0.0.1

とりあえず、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

http://127.0.0.1

補足: スクリーンショット

ホーム画面

ScreenShot 2020-08-10 2.48.30.png

右上にLOGIN, REGISTERのメニューが追加されています。

登録画面

ScreenShot 2020-08-10 13.52.16.png

ログイン画面

ScreenShot 2020-08-10 13.52.50.png

ログイン後のホーム画面(ダッシュボード)

ScreenShot 2020-08-10 13.52.27.png

リセットパスワード画面

ScreenShot 2020-08-10 13.53.41.png

ScreenShot 2020-08-10 13.53.54.png

ScreenShot 2020-08-10 13.53.59.png

ScreenShot 2020-08-10 13.54.23.png

ScreenShot 2020-08-10 13.55.02.png

パスワードリセットのメールはMailHogで確認しています。

補足: webコンテナのNode.js

webコンテナ内にNode(npm, yarn)が入ってますが、コンテナ内でビルドするのは非常に時間がかかってしまうので実際の開発ではMacローカルにNodeを入れて実行させるのが良いです。

補足: Nodeバージョン固定化

特定のバージョンのNode.jsでしか動かして欲しくない場合、package.jsonenginesフィールドにNode.jsのバージョンを明記しておくとyarn installnpm 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-versionpackage.json と同じディレクトリに配置しておくと、nodenvが自動的にNodeのバージョンを切り替えてくれて便利です。

.node-version
14.2.0

参考

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

シンプルな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);
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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

最後に

なんともややこしい。
理解したはずなのに、記事書いてたときにごちゃごちゃしました。笑
間違っていたら教えて下さい。

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

【PHP8.0】PHP8.0の新機能

PHP8.0 / PHP7.4

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() {}

これまではPHPDocPSR-5のようにコメントで書くしかなく、強制力もありませんでした。
アトリビュートは、PHPの構文として実効力のある方法でメタデータを書けるようになります。

Attribute Amendments

複数件の投票がありますが、全て受理されています。
アトリビュートの細かな追加仕様のRFCです。

アトリビュートをカンマ区切りで複数書けるようにする、PhpAttributeだったクラス名をAttributeにする、などの仕様追加です。
アトリビュート自体の大きな変更はないようです。

Shorter Attribute Syntax

アトリビュートの構文変更のRFCです。

アトリビュートの構文には幾つか問題があります。

<<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 warningsConsistent type errors for internal functionsStricter 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

このせいで色々と面倒なことになっていました。
特に 123123 が異なるのはとても違和感がありますね。

ということで、PHP8では数値型文字列の定義をひとつにします。

・数値型文字列:0個以上のスペース+数値+0個以上のスペース
・それ以外は全て数値型文字列ではない。

これにより 123123 は緩やかに同じ値になります。
そして、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以降OK

Allow 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までOK
class Foo {
    public function __get(array $name): void{}
}

今後はこれが禁止され、正しい型しか書けないようになります。
もしくは、単に何も書かないかです。

PHP8.0
class 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までOK
trait 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.0
trait 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のソートはこれまで不安定ソートだったので、ソート後にabの順番、cdの順番が保証されませんでした。
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以降OK

Static return type

賛成54反対0で受理。
返り値にstaticを書けるようにするRFCです。

PHP7では、返り値としてparentselfを書くことが可能です。

<?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からOK

DOM 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で十分なんだよめんどくせーコマンドなんていちいち打ちたくねーんだよもっと手抜きさせろ。

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