Quantcast
Browsing Latest Articles All 54 Live
Mark channel Not-Safe-For-Work? (0 votes)
Are you the publisher? or about this channel.
No ratings yet.
Articles:

docker-composeでWordPress5.3+mysql8.0+PHP7.4+apache2.4を動かす

こんばんは、モバイルコムの派手髪VPoE石黒です。

タイトルの通り、最新バージョンのWordPressを動かしてみた話です。
現時点で最新バージョンのインストール方法が意外と見つからなかったのでメモです。
以下のdocker-compose.ymlでupしてください。

docker-compose.yml
version:'3'services:db:image:mysql:8.0environment:MYSQL_USER:wordpressMYSQL_PASSWORD:wordpressMYSQL_DATABASE:wordpressMYSQL_ROOT_PASSWORD:wordpressvolumes:-./data/db:/var/lib/mysqlrestart:alwayswordpress:depends_on:-dbimage:wordpress:php7.4-apacheports:-"8000:80"restart:alwaysenvironment:WORDPRESS_DB_HOST:db:3306WORDPRESS_DB_PASSWORD:wordpress

ポイントは、wordpressイメージのタグをphp7.4-apacheにしている箇所。
タグ一覧はこちらを参照 https://hub.docker.com/_/wordpress/
現状のlatestではphp7.3がインストールされ、mysql8で動かそうとすると以下のエラーに見舞われます。

The server requested authentication method unknown to the client [caching_sha2_password]")

mysql5.7にダウングレードしたり、mysqlの起動オプションを変更したり、設定を読み込ませることでも解決できるようですが、
php7.4のイメージだと解消するようです。(しました)
どこかの掲示板にて、php7.4-fpm-alpineを指定しろというコメントも見つけたのですが、
それだとapacheモジュールが含まれないため、別途httpインスタンスを立ち上げないといけない模様です。
面倒なのでapacheの含まれているイメージを指定したところ、一撃でした。

おやすみなさい。

php7.4から配列でない値に配列のようにアクセスしたらエラーになる

php7.3からphp7.4への下位互換性のない変更点の1つとして
配列じゃない値に配列のようにアクセスすることができなくなりました。

ちなみにnullに対して以下のようにアクセスすると以下のようになります。

$hoge=null;var_dump($hoge[0]);exit;
出力結果
// php7.3までNULL// php7.4Tryingtoaccessarrayoffsetonvalueoftypenullin/hoge.phponline3trace=>#0 ...

null, bool, int, float または resource 型を ($null["key"] のように) 配列としてアクセスしようとすると、警告が生成されるようになりました。

https://www.php.net/manual/ja/migration74.incompatible.php

地味ですが、今までがよしなに動き過ぎていて、影響範囲が大きめの割と面倒な対応でした。

DockerでCentOS8+apache2.4+php7.4環境を作りwordpressを動かす環境を作る

現在借りているVPSサーバのスペックが低くく、過去にはGoogleさんに100点満点中2点という、あまりにひどい点を頂いたので、それを改善すべく奮闘中です。

GWにはVPSのサーバの乗り換えもしくはスケールアップを行いますが、その際、上モノ(Apahce+php)はいつでもバージョンを上げていけるよう。Dockerを使ってコンテナ化してみようと画策。

まずはタイトルに挙げた環境で環境を作ろうとしてみました。

DockerでCentOS8+apache2.4+php7.4環境を作る

Dockerfileについて

DockerFleとして用意したのは下記のとおりです。

# docker-hubからOSのイメージ引込FROM centos:centos8.1.1911# サーバの日付合わせRUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# epel-releaseのインストールRUN yum install-y epel-release &&\
    yum clean all

# remiのインストールRUN rpm -ivh http://ftp.riken.jp/Linux/remi/enterprise/remi-release-8.rpm

# yumのアップデートRUN yum update -y&&\
    yum clean all

# 必要なモジュールのインストール(apache)RUN yum -yinstall httpd &&\
    yum clean all

# 必要なモジュールのインストール(php7.4)RUN yum -yinstall php74-php php74-php-mysqli php74-php-gd php74-php-mbstring php74-php-opcache php74-php-xml

# phpコマンドを作成RUN ln /usr/bin/php74 /usr/bin/php

# wordpressインストールディレクトリの所有者をapacheユーザに変更                                              RUN chown-R apache:apache /var/www/html

# php74−php−fpmのサービス永続化RUN systemctl enable php74-php-fpm

# apache サーバのサービス永続化RUN systemctl enable httpd 

# 接続用ポートの穴あけEXPOSE 80

Dokerfileの細かな内容についてはDockerfileの補足で説明していますのでそちらもご覧ください。

dockerイメージの作成

Dockerfileの保存ディレクトリで下記のコマンドを実行

docker build -t wordpress:0.0.1 ./

-tのあとはタグ名なので、wordpressで動かすことを考えてwordpress:0.0.1としました。けど、翌々考えたらwordpressは今回インストールしていないので、名前は変えても良さそうですね。

dockerコンテナの起動

イメージができたらコンテナを起動。

 docker run --privileged --name wordpress -p 80:80 -v [localhostのディレクトリ]:/var/www/html -itd wordpress:0.0.1 /sbin/init

ブラウザで起動確認

dockerコンテナ起動時に指定した「localhostのディレクトリ」にwordpressのモジュールを入れておくと下記のように「wordpressへようこそ」画面が出るはず。ここまでくれば、この記事のゴールです。

スクリーンショット 2020-04-02 21.19.17.png

ここまでたどり着くまでいろいろ調べまくりました。
もともとあるコンテナを使えばもっと手軽にできたと思いますが、勉強がてらDockerfileを作るところから始めたので思いの外時間がかかりましたが、勉強になりましたね。こうやって記事にすることで後で見直すこともできるので一石二鳥です。

Dockerfileの設定の補足

docker-hubからOSのイメージ引込

FROM centos:centos8.1.1191

OSコンテナの引き込みですね。latestでも良かったのですが、一旦執筆時点の最新のバージョンを指定しています。最終的にはlatestにするかもしれません。

サーバの日付合わせ

RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

コンテナの時刻を日本時間に合わせるコマンドです。
ログなどを保管する際、時刻がおかしなことになっていると色々と面倒なので、必要な設定となりますね。

epel-releaseのインストール

RUN yum install -y epel-release &&\
    yum clean all

remiのインストール

RUN rpm -ivh http://ftp.riken.jp/Linux/remi/enterprise/remi-release-8.rpm

php7.4をインストールするためにremiレポジトリをインストールする必要がありますが、まずここにたどり着くまで苦労しました。
CentOS8用のrpmをインストールしないと当然動かないのですが、ネットではremi-release-7.rpmでしか記事が見つからず、適当に7→8に書き換えたらインストールできました。

remiのインストールのに結構手こずりました。

yumのアップデート

RUN yum update -y &&\
    yum clean all

yumのアップデートは必須作業なので、apache、php7.4のインストール前に実行しておきます。

必要なモジュールのインストール(apache)

RUN yum -y install httpd &&\
    yum clean all

apacheのインストールはyum経由ですので特に何も設定は必要ありませんね。

必要なモジュールのインストール(php7.4)

RUN yum -y install php74-php php74-php-mysqli php74-php-gd php74-php-mbstring php74-php-opcache php74-php-xml

phpのモジュールのインストールはphp74でもインストールされますが、動作させるにはphp74-php-xxx といったphp74とモジュールの間にーphpを入れる必要があるというところに行き着くまでにまた手こずりました。

phpコマンドを作成

RUN ln /usr/bin/php74 /usr/bin/php

php7.4を入れた後、phpとコマンドを実行しても何も反応がなく、いろいろ調べた結果、php74がphpのコマンドであることが判明。
このままだと、phpとしてコマンドが実行できないので、リンクを張ってphpでもコマンドが実行できるようにしました。

wordpressインストールディレクトリの所有者をapacheユーザに変更

RUN chown -R apache:apache /var/www/html

apacheサーバの起動ユーザに合わせてwordpress配置ディレクトリのオーナーを変更。

php74-php-fpmのサービス永続化

RUN systemctl enable php74-php-fpm

apacheをインストールしてもwordpresの画面が立ち上がらず何故かと調べていたらphp-fpmがサービスとして起動している費強があるということがわかりました。
コンテナ起動時に自動でサービスが起動するようにサービスを永続化させるようにしました。

apache サーバのサービス永続化

RUN systemctl enable httpd 

apacheもphp-fpm同様サービスとして常に起動するように設定しました。

接続用ポートの穴あけ

EXPOSE 80

http接続できるように80番のポートを開けます。https化を狙って443も開けておく必要がありますが、それはこれからGWまでの課題ですね。

今後の課題

  • https化
  • mysqlとの接続

といったところでしょうか。wordpressの画面は起動しましたがwordpressまではインストールできていないです。
なので、GWまでにはこのあたりを解決したいですね。

PHPエラー一覧

趣味でphpを実装して遊んでいます。(初心者)
それにあたり出力されたエラーと、その原因と対処法を記載していきます。

実施環境

  • Windowsのエディション:Windows 10 Home
  • PHPのバージョン:PHP 7.4.2
  • 使用しているエディタ:Visual Stadio Code

エラー一覧

Notice: Undefined index

  • エラー
    Notice: Undefined index: A1_No in C:\xampp~~\index.php on line 7

  • 原因
    呼び出した配列のキーがセットされていませんよ。と言っている。

index.php
//$_POST[]の中の"A1_No"を表す$A1_No=$_POST["A1_No"]
  • 対処法
    • 配列のキーが必ずセットされるように修正する。(※修正方法は実装によるため、ここでは割愛)
    • issetメソッドを使用して配列が呼び出されていることを確認し、配列がセットされていない場合の対処を埋め込む。以下の例の場合は、エラーを無視する処理を埋め込む。
index.php
<?php//フォームより送信された値を取得する$A1_Yes=null;$A1_No=null;$_POST[]=null;//配列がセットされている場合はtrueを返すif(isset($_POST["A1_No"])){$A1_No=$_POST["A1_No"];echo'$_POST["A1_No"]はセットされています。';}else{//エラー制御演算子'@'(エラーを無視する)@$A1_No=$A1_No;}?>

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

PHP をインストールすると php.ini-developmentphp.ini-productionの2つのファイルが用意されていますが、どのような違いがあるのか分からなかったので diff を取ってみました。

環境

  • PHP 7.4.5

diff する

$docker run php:7.4.5-cli diff -u /usr/local/etc/php/php.ini-development /usr/local/etc/php/php.ini-production > diff.txt

diff 結果

--- /usr/local/etc/php/php.ini-development  2020-04-17 11:30:36.000000000 +0000
+++ /usr/local/etc/php/php.ini-production   2020-04-17 11:30:36.000000000 +0000
@@ -83,7 +83,7 @@
 ; development version only in development environments, as errors shown to
 ; application users can inadvertently leak otherwise secure information.

-; This is the php.ini-development INI file.
+; This is the php.ini-production INI file.

 ;;;;;;;;;;;;;;;;;;;
 ; Quick Reference ;
@@ -363,7 +363,9 @@

 ; Allows to include or exclude arguments from stack traces generated for exceptions
 ; Default: Off
-zend.exception_ignore_args = Off
+; In production, it is recommended to turn this setting on to prohibit the output 
+; of sensitive information in stack traces
+zend.exception_ignore_args = On

 ;;;;;;;;;;;;;;;;;
 ; Miscellaneous ;
@@ -460,7 +462,7 @@
 ; Development Value: E_ALL
 ; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
 ; http://php.net/error-reporting
-error_reporting = E_ALL
+error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

 ; This directive controls whether or not and where PHP will output errors,
 ; notices and warnings too. Error output is very useful during development, but
@@ -477,7 +479,7 @@
 ; Development Value: On
 ; Production Value: Off
 ; http://php.net/display-errors
-display_errors = On
+display_errors = Off

 ; The display of errors which occur during PHP's startup sequence are handled
 ; separately from display_errors. PHP's default behavior is to suppress those
@@ -488,7 +490,7 @@
 ; Development Value: On
 ; Production Value: Off
 ; http://php.net/display-startup-errors
-display_startup_errors = On
+display_startup_errors = Off

 ; Besides displaying errors, PHP can also log errors to locations such as a
 ; server-specific log, STDERR, or a location specified by the error_log
@@ -525,7 +527,9 @@
 ; This setting is on by default.
 ;report_zend_debug = 0

-; Store the last error/warning message in $php_errormsg (boolean).
+; Store the last error/warning message in $php_errormsg (boolean). Setting this value
+; to On can assist in debugging and is appropriate for development servers. It should
+; however be disabled on production servers.
 ; This directive is DEPRECATED.
 ; Default Value: Off
 ; Development Value: Off
@@ -1186,7 +1190,7 @@

 ; Enable / Disable collection of memory usage statistics by mysqlnd which can be
 ; used to tune and monitor MySQL operations.
-mysqlnd.collect_memory_statistics = On
+mysqlnd.collect_memory_statistics = Off

 ; Records communication from all extensions using mysqlnd to the specified log
 ; file.
@@ -1559,7 +1563,7 @@
 ; Development Value: 1
 ; Production Value: -1
 ; http://php.net/zend.assertions
-zend.assertions = 1
+zend.assertions = -1

 ; Assert(expr); active by default.
 ; http://php.net/assert.active
@@ -1586,8 +1590,6 @@
 ; http://php.net/assert.quiet-eval
 ;assert.quiet_eval = 0

-
-
 [COM]
 ; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs
 ; http://php.net/com.typelib-file
@@ -1881,7 +1883,7 @@

 ; Enables or disables copying of PHP code (text segment) into HUGE PAGES.
 ; This should improve performance, but requires appropriate OS configuration.
-;opcache.huge_code_pages=0
+;opcache.huge_code_pages=1

 ; Validate cached file permissions.
 ;opcache.validate_permission=0

zend.exception_ignore_args

  • development は Off、production は On
  • 有効にすると例外のスタックトレースに引数情報が出なくなる
  • PHP7.4以降の設定

error_reporting

http://php.net/error-reporting

エラー出力レベルを設定します。

  • development は E_ALL、production は E_ALL & ~E_DEPRECATED & ~E_STRICT
  • E_ALL は 全ての PHP エラーを表示する
  • E_ALL & ~E_DEPRECATED & ~E_STRICT は 非推奨の警告エラーを除く PHP エラーを表示する
    • E_DEPRECATED は コードの相互運用性や互換性を維持するために PHP がコードの変更を提案する
    • E_STRICT は 実行時の注意、将来のバージョンで動作しなくなるコードについて警告する
  • https://www.php.net/manual/ja/errorfunc.constants.php

display_errors

http://php.net/display-errors

エラーをHTML出力の一部として画面に出力するかどうかを定義します。

  • development は On、production は Off
  • セキュリティ上、本番では Off推奨

display_startup_errors

http://php.net/display-startup-errors

display_errorsをonにした場合でも、PHPの起動シーケンスにおいて発生したエラーは表示されません。

  • development は On、production は Off
  • セキュリティ上、本番では Off推奨

mysqlnd.collect_memory_statistics

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

さまざまなメモリ統計情報の収集を有効にします。

  • development は On、production は Off
  • phpinfo()mysqliの統計情報を出力するかどうか

zend.assertions

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

アサーションのコードを生成して実行します

  • development は 1、production は -1
  • 1 アサーションのコードを生成して実行します (開発モード)
  • 0 アサーションのコードは生成しますが実行時にはスキップします (実行しません)
  • -1 アサーションのコードを生成せず、アサーションのコストがゼロになります (実運用モード)

opcache.huge_code_pages

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

PHPコード(textセグメント)を HUGE PAGE にコピーする機能を有効にしたり、無効にしたりできます。

  • development は 0、production は 1
  • パフォーマンスは向上するはずですが、適切なOSの設定が必要です。

(適切なOSの設定って何...? わからないので 0 にしてます。)

参考

PHP7.4 ぼくのかんがえたさいきょうのphp.ini

ストーリー

PHPをインストールしたら必ず行う php.iniの設定ですが、
ネット上ではPHP5系の情報がたくさん出回っており、非推奨または削除された設定例が数多く困り果てていました。

良い感じにまとめてくれてるサイトが見つからなかったので、最強でベストプラクティスな php.ini推奨設定を考えました。
異論は受け付けますので、ぜひコメントください。

参考設定

PHPでは、開発用と本番用の設定例を用意してくれています。
なんと素晴らしいことなんでしょうか。これをベースに設定します。

予め以前の記事で設定の差分を調べておきましたので、よかったらご覧ください。

環境

  • PHP 7.4.5 (執筆時のバージョンです。)

※バージョンが異なる場合は公式サイトで有効な設定か確認してください。

開発用 php.ini

php.ini
zend.exception_ignore_args=offexpose_php=onmax_execution_time=30max_input_vars=1000upload_max_filesize=64Mpost_max_size=128Mmemory_limit=256Merror_reporting=E_ALLdisplay_errors=ondisplay_startup_errors=onlog_errors=onerror_log=/var/log/php/php-error.logdefault_charset=UTF-8[Date]date.timezone=Asia/Tokyo[mysqlnd]mysqlnd.collect_memory_statistics=on[Assertion]zend.assertions=1[mbstring]mbstring.language=Japanese

本番用 php.ini

php.ini
zend.exception_ignore_args=onexpose_php=offmax_execution_time=30max_input_vars=1000upload_max_filesize=64Mpost_max_size=128Mmemory_limit=256Merror_reporting=E_ALL & ~E_DEPRECATED & ~E_STRICTdisplay_errors=offdisplay_startup_errors=offlog_errors=onerror_log=/var/log/php/php-error.logdefault_charset=UTF-8[Date]date.timezone=Asia/Tokyo[mysqlnd]mysqlnd.collect_memory_statistics=off[Assertion]zend.assertions=-1[mbstring]mbstring.language=Japanese[opcache]opcache.enable=1opcache.memory_consumption=128opcache.interned_strings_buffer=8opcache.max_accelerated_files=4000opcache.validate_timestamps=0opcache.huge_code_pages=0opcache.preload=/var/www/preload.phpopcache.preload_user=www-data

オプションの補足

設定値だけだと何を設定しているかわからないので、各項目の補足を付け加えました。

zend.exception_ignore_args

https://www.php.net/manual/ja/migration74.other-changes.php

  • 開発 は off、本番 は on
  • 有効にすると例外のスタックトレースに引数情報が出なくなる
  • PHP7.4以降の設定

expose_php

https://www.php.net/manual/ja/ini.core.php#ini.expose-php

  • 開発 は on、本番 は off
  • 有効にするとHTTPヘッダに X-Powered-By: PHP/7.4.5とPHPのバージョン情報が表示されます。

max_execution_time

https://www.php.net/manual/ja/info.configuration.php#ini.max-execution-time

  • 設定値: 30(秒) デフォルト: 30(秒)
  • 1リクエストあたりの最大実行時間(秒)
  • コマンドラインから実行する場合のデフォルト設定は 0 です。
  • サーバーの負荷を上げることを防止するのに役立ちます。

重たい処理を実行するとこの設定で引っかかるので、その場合はそのコードだけ特別にset_time_limitを呼んであげると良いかもです。

max_input_vars

https://www.php.net/manual/ja/info.configuration.php#ini.max-input-vars

  • 設定値: 1000(個) デフォルト: 1000(個)
  • 1リクエストで受け付ける最大の入力変数の数
  • $_GET, $_POST, $_COOKIEそれぞれ個別に適用されます。
  • 設定値を超える場合は E_WARNINGが発生し、以降の入力変数はリクエストから削除されます。

入力フォームが気が狂ったように多い画面とかは1000超えるかも😇

upload_max_filesize

https://www.php.net/manual/ja/ini.core.php#ini.upload-max-filesize

  • 設定値: 20M デフォルト: 2M
  • アップロードされるファイルの最大サイズ。

スマホの写真サイズも大きいので多めにした方が良き

post_max_size

https://www.php.net/manual/ja/ini.core.php#ini.post-max-size

  • 設定値: 128M デフォルト: 8M
    • upload_max_filesize の設定値より大きくする必要がある。
  • POSTデータに許可される最大サイズを設定します。
  • ファイルアップロードにも影響します。

memory_limit

https://www.php.net/manual/ja/ini.core.php#ini.memory-limit

  • 設定値: 256M デフォルト: 128M
    • post_max_size の設定値より大きくする必要がある。
    • memory_limit > post_max_size > upload_max_filesize
  • 1リクエストあたりの最大メモリ使用量

メモリ設定はサーバーやプロジェクトによるかと思います。
最初から大量に確保するのではなく、必要に応じて上げていくのが良いのかなと思います。

error_reporting

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

  • 開発 は E_ALL、本番 は E_ALL & ~E_DEPRECATED & ~E_STRICT
  • E_ALLは 全ての PHP エラーを表示する
  • E_ALL & ~E_DEPRECATED & ~E_STRICTは 非推奨の警告エラーを除く PHP エラーを表示する。
    • E_DEPRECATEDは コードの相互運用性や互換性を維持するために PHP がコードの変更を提案する。
    • E_STRICTは 実行時の注意、将来のバージョンで動作しなくなるコードについて警告する。

display_errors

http://php.net/display-errors

  • 開発 は on、本番 は off
  • エラーをHTML出力の一部として画面に出力するかどうかを定義します。
  • セキュリティ上、本番では off推奨

display_startup_errors

http://php.net/display-startup-errors

  • 開発 は on、本番 は off
  • display_errorsonにした場合でも、PHPの起動シーケンスにおいて発生したエラーは表示されません。
  • セキュリティ上、本番では off推奨

log_errors

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

  • エラーメッセージを、サーバーのエラーログまたはerror_logに記録するかどうかを指します。
  • このオプションはサーバーに依存します。

error_log

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

  • スクリプトエラーが記録されるファイル名です。

default_charset = UTF-8

https://www.php.net/manual/ja/ini.core.php#ini.default-charset

  • 設定値: UTF-8 デフォルト: UTF-8
  • デフォルト文字コード設定

PHP 5.6.0 以降は "UTF-8" がデフォルトになりますが、念のため明示的に指定します。

date.timezone

https://www.php.net/manual/ja/datetime.configuration.php#ini.date.timezone

  • 設定値: Asia/Tokyo デフォルト: GMT
  • 全ての日付/時刻関数で使用されるデフォルトのタイムゾーン。

mysqlnd.collect_memory_statistics

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

  • 開発 は on、本番 は off
  • さまざまなメモリ統計情報の収集を有効にします。
  • phpinfo()mysqliの統計情報を出力するかどうか

zend.assertions

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

  • 開発 は 1、本番 は -1
  • アサーションのコードを生成して実行します
  • 1 アサーションのコードを生成して実行します (開発モード)
  • 0 アサーションのコードは生成しますが実行時にはスキップします (実行しません)
  • -1 アサーションのコードを生成せず、アサーションのコストがゼロになります (実運用モード)

mbstring.language

https://www.php.net/manual/ja/mbstring.configuration.php#ini.mbstring.language

  • 設定値: Japanese デフォルト: neutral
  • mbstring で使用される言語設定のデフォルト値。

opcache の設定

https://www.php.net/manual/ja/opcache.configuration.php

本番のみ有効にします。
opcacheするとソースコードのキャッシュ、最適化して高速化が見込めます。

ソースコードを変更してもサーバーを再起動しないと変更が反映されなくなるため開発時は使用しません。

opcache.enable

  • オペコード・キャッシュを有効にします。

opcache.memory_consumption

  • OPcache によって使用される共有メモリ・ストレージのサイズ(MB単位)

opcache.interned_strings_buffer

  • インターン (intern) された文字列を格納するために使用されるメモリ量。(MB単位)

opcache.max_accelerated_files

  • OPcache ハッシュテーブルのキー(すなわちスクリプト)の最大数

opcache.validate_timestamps

  • 有効にすると、OPcache は、スクリプトが更新されたか opcache.revalidate_freq 秒ごとにチェックします。
  • 無効にすると、スクリプトの更新をチェックしません。

opcache.huge_code_pages

  • PHPコード(textセグメント)を HUGE PAGE にコピーする機能を有効にしたり、無効にしたりできます。
  • これにより、パフォーマンスは向上するはずですが、適切なOSの設定が必要です。

※適切なOS設定がいまいちわからなかったので、この設定は無効化しています。

opcache.preload

  • サーバが起動した際にコンパイルされ、実行されるPHPスクリプトを指定します。
  • PHP7.4以降の設定

※ここはプロジェクトに合わせて自前で用意する必要があります。これは無理に設定しなくてもokと思います。
※Laravelの場合はこちらのコードを参考にしています。 https://github.com/brendt/laravel-preload/blob/master/preload.php

opcache.preload_user

その他

論理値

設定で使用される論理値(true, false, on, off, yes, no)は大文字・小文字は区別しないようなので、True, On等でも認識されます。
とても柔軟で素敵だと思いました😇😇😇

私のphp.iniはどこ?

ここです。

$php -i | grep php.ini

環境変数を使いたい

普通に環境変数読み込めます。

php.ini
date.timezone=$TZ

参考

俺のLaravelがこんなに遅いわけがない

Laravel

環境(ベース)

  • PHP 7.4.5
  • Laravel 7.5.1

Docker for Macでnginxとphp-fpmコンテナをunixソケットで繋いだ環境で試してます。

環境の差異

  1. OPcache なし
  2. OPcache あり、プリロードなし
  3. OPcache なし、プリロードあり

比較方法

  • Laravelのwelcome画面をabコマンドの結果で比較します。

Requests per second(1秒間に捌けるリクエスト数)、Time per request(1リクエストあたりの処理時間)に着目します。

以前こんな記事も書いてます。
Apache Bench を初めて使ってみた

OPcacheなし

php.ini(設定例)
[opcache]opcache.enable=0
$ ab -n 1000 -c 100 http://127.0.0.1/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.17.8
Server Hostname:        127.0.0.1
Server Port:            80

Document Path:          /
Document Length:        2426 bytes

Concurrency Level:      100
Time taken for tests:   18.432 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      3446000 bytes
HTML transferred:       2426000 bytes
Requests per second:    54.25 [#/sec] (mean)
Time per request:       1843.175 [ms] (mean)
Time per request:       18.432 [ms] (mean, across all concurrent requests)
Transfer rate:          182.58 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   2.5      0      22
Processing:    79 1744 326.5   1836    2038
Waiting:       69 1743 326.6   1834    2038
Total:         83 1745 324.6   1836    2039

Percentage of the requests served within a certain time (ms)
  50%   1836
  66%   1865
  75%   1883
  80%   1894
  90%   1926
  95%   1948
  98%   1988
  99%   2009
 100%   2039 (longest request)
  • 1秒間に捌けるリクエスト数: 54.25
  • 1リクエストあたりの処理時間: 18.432 (ms)

俺のLaravelがこんなに遅いわけがない...

OPcacheあり、プリロードなし

php.ini(設定例)
[opcache]opcache.enable=1opcache.memory_consumption=128opcache.interned_strings_buffer=8opcache.max_accelerated_files=4000opcache.validate_timestamps=0opcache.huge_code_pages=0
$ ab -n 1000 -c 100 http://127.0.0.1/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.17.8
Server Hostname:        127.0.0.1
Server Port:            80

Document Path:          /
Document Length:        2426 bytes

Concurrency Level:      100
Time taken for tests:   3.318 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      3446000 bytes
HTML transferred:       2426000 bytes
Requests per second:    301.41 [#/sec] (mean)
Time per request:       331.772 [ms] (mean)
Time per request:       3.318 [ms] (mean, across all concurrent requests)
Transfer rate:          1014.32 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2   4.4      1      24
Processing:    31  316  60.1    319     481
Waiting:        9  311  59.3    316     461
Total:         34  318  57.9    320     483

Percentage of the requests served within a certain time (ms)
  50%    320
  66%    335
  75%    346
  80%    354
  90%    381
  95%    404
  98%    413
  99%    421
 100%    483 (longest request)
  • 1秒間に捌けるリクエスト数: 301.41
  • 1リクエストあたりの処理時間: 3.318 (ms)

54.25から 301.41へおよそ5.55倍の高速化されました!!
圧倒的すぎる速さ!!キャッシュ効果恐るべし!!

OPcacheあり、プリロードあり

php.ini(設定例)
[opcache]opcache.enable=1opcache.memory_consumption=128opcache.interned_strings_buffer=8opcache.max_accelerated_files=4000opcache.validate_timestamps=0opcache.huge_code_pages=0opcache.preload=/var/www/preload.phpopcache.preload_user=www-data

https://github.com/brendt/laravel-preload/blob/master/preload.php

このpreload.phpを参考にしてます。ignoreにいくつか追加してます。
ただお試し的に使ってるので、また内容まとまったら別記事にしたいと思います。

$ ab -n 1000 -c 100 http://127.0.0.1/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.17.8
Server Hostname:        127.0.0.1
Server Port:            80

Document Path:          /
Document Length:        2426 bytes

Concurrency Level:      100
Time taken for tests:   2.878 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      3446000 bytes
HTML transferred:       2426000 bytes
Requests per second:    347.40 [#/sec] (mean)
Time per request:       287.850 [ms] (mean)
Time per request:       2.878 [ms] (mean, across all concurrent requests)
Transfer rate:          1169.09 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   1.3      0       7
Processing:    49  268  46.5    275     344
Waiting:       35  267  46.5    274     342
Total:         54  269  45.5    276     344

Percentage of the requests served within a certain time (ms)
  50%    276
  66%    287
  75%    294
  80%    298
  90%    312
  95%    322
  98%    331
  99%    335
 100%    344 (longest request)
  • 1秒間に捌けるリクエスト数: 347.40
  • 1リクエストあたりの処理時間: 2.878 (ms)

301.41から 347.40へおよそ1.15倍の高速化されました!!

もう俺のLaravelが遅いなんて言わせない...!!

まとめ

# OPcacheなし
Requests per second:    54.25 [#/sec] (mean)
Time per request:       18.432 [ms] (mean, across all concurrent requests)

# OPcacheあり、プリロードなし
Requests per second:    301.41 [#/sec] (mean)
Time per request:       3.318 [ms] (mean, across all concurrent requests)

# OPcacheあり、プリロードあり
Requests per second:    347.40 [#/sec] (mean)
Time per request:       2.878 [ms] (mean, across all concurrent requests)

何も設定してない 54.25の状態から 347.40へおよそ6.4倍と劇的な高速化を遂げました!!!
PHPのポテンシャル半端ないですね!!!

プリロード自体は初めてなので諸々問題出てくるかもしれませんが、何か問題あったらまた記事にしていきたいと思います😃

PHP7.4でもマルチスレッド!!

以前「PHP7.4からのマルチスレッド」という記事を書きましたが、内容が薄すぎたのでもう少し紹介していきたいと思います。コードは全く載せていないのでサンプルはParallelのテストコードを見てください。

なお、PHPにおけるマルチスレッドはCLI限定となります。マルチプロセスを実現するpcntlではwebサーバで使用することを非推奨としていますが、pthreadsやparallelではコンパイルから拒否されます。

拡張モジュール

krakjoe氏が開発しているParallelを使用します。
https://github.com/krakjoe/parallel

マルチスレッドモデル

ParallelはGo言語のgoroutineに大きく影響を受けています。
https://www.php.net/manual/ja/philosophy.parallel.php

parallel\Runtime

RuntimeはpthreadsのWorkerにかなり近いです。
インスタンスが生成されたときにスレッドが起動し、破棄されるときまたはclose()やkill()が呼ばれたときにスレッドをシャットダウンします。run()を呼ぶと引数で指定したクロージャがスケジュールされ、即座にFutureを返します。スケジュールされたクロージャは順次実行されます。

parallel\Future

何の変哲もないFutureパターンの実装です。
cancel()によりスケジュールされたタスクを中断させることができます。

parallel\Channel

仕様はgoroutineのチャンネルとほぼ同じです。Parallelではチャンネルに文字列で名前を設定することができます。匿名でも勝手に名前が付きます。open()によって好きな場所からチャンネル名を指定して特定のチャンネルを得ることができます。また、バッファのサイズを無制限にできます。PHPっぽさが感じられますね。

parallel\Events

複数のFutureやChannelを束ねてデータの受信をイベントとするイベントストリームを提供します。また、poll()でイベントを受け取れますが、データの送信元となったFutureやChannelはイベントストリームから削除されます。継続して同じChannelからのイベントを受け取りたい場合、Event\Type::ReadであろうとEvent\Type::WriteであろうとaddChannel()によって再登録が必要です。

parallel\Events\Input

複数のチャンネルにそれぞれ指定したデータを送信する操作を定義することができます。1つのチャンネルに複数のデータを送信することはできません。Eventsにセットすることで、ストリームが動き始めたタイミングで各チャンネルにデータを送信され、Event\Type::Writeのイベントが発生します。また、Inputに設定した項目は送信されたものから削除されていきます。

parallel\Sync

昔ながらの低レベルの実装をしたければこれを使いましょう。ほとんどのケースではChannelの方が優れているとのこと。

PHPのCallableについて完全に理解する

はじめに

Callableという擬似型は PHP 5.4系で導入されていたようなのですが、ある値が Callableかどうかがどのように決まるのかさっぱり理解できず、業務で使う機会もそんなになかったことから放置していました。

業務で使う機会が出てきたというわけでも全くもって全くないのですが、ふと考えてみたら完全に理解できた気がしてしまったので、ツッコミ覚悟で記事にまとめることにしました。

この記事ではどんな値を is_callable()に渡したら trueを返されるのかを探っていきます。 is_callable()trueを返す値はそのまま call_user_func() , call_user_func_array()などに渡して呼び出すことが可能です。

var_dump()を含んだコードの動作確認は paiza.IOで行っています。執筆時のPHPバージョンは PHP 7.4.1でした。

この記事のスコープ外

「関数やクラスがどの時点で定義されるのか」というのは、これはこれで複雑な話題です。

この記事では、「関数やクラスが定義されているかどうか」を明確にした上で Callableかどうかの判定方法を理解したいため、関数やクラスの定義方法は最もシンプルなもののみを用います。

<?php// シンプルな関数定義functionsomeFunction(){}// シンプルなクラス定義classSomeClass(){publicstaticfuncionSomeClassMethod(){}publicfunctionSomeObjectMethod(){}}// シンプルでない関数定義たちif(true){functionanotherFunction(){}}functiondefineYetAnotherFunction(){functionyetAnotherFunction(){}}// シンプルでないクラス定義たちif(true){classAnotherClass(){publicstaticfuncionAnotherClassMethod(){}publicfunctionAnotherObjectMethod(){}}}functiondefineYetAnotherClass(){classYetAnotherClass(){publicstaticfuncionYetAnotherClassMethod(){}publicfunctionYetAnotherObjectMethod(){}}}

::を含まない文字列

文字列と同じ名前の関数が定義されている場合は Callableであり、そうでない場合は Callableでない、と判定されます。

<?phpfunctionis_odd(int$int):bool{return$int%2===1;}var_dump(is_callable('is_odd'));// => bool(true)var_dump(is_callable('is_even'));// => bool(false)

::の両側になんかある文字列

::の左側の文字列と同じ名前のクラスが定義されていて、かつ ::の右側の文字列と同じ名前のメソッドが定義されていて publicな場合は Callableであり、そうでない場合は Callableでないと判定されます。

<?phpclassSomeClass{publicstaticfunctionsomePublicClassMethod(){}protectedstaticfunctionsomeProtectedClassMethod(){}privatestaticfunctionsomePrivateClassMethod(){}publicfunctionsomePublicObjectMethod(){}protectedfunctionsomeProtectedObjectMethod(){}privatefunctionsomePrivateObjectMethod(){}}var_dump(is_callable('SomeClass::somePublicClassMethod'));// => bool(true)var_dump(is_callable('SomeClass::someProtectedClassMethod'));// => bool(false)var_dump(is_callable('SomeClass::somePrivateClassMethod'));// => bool(false)var_dump(is_callable('SomeClass::somePublicObjectMethod'));// => bool(true)var_dump(is_callable('SomeClass::someProtectedObjectMethod'));// => bool(false)var_dump(is_callable('SomeClass::somePrivateObjectMethod'));// => bool(false)var_dump(is_callable('AnotherClass::somePublicClassMethod'));// => bool(false)var_dump(is_callable('AnotherClass::somePublicObjectMethod'));// => bool(false)var_dump(is_callable('SomeClass::anotherPublicClassMethod'));// => bool(false)var_dump(is_callable('SomeClass::anotherPublicObjectMethod'));// => bool(false)

クラスのメソッド内で Callableかどうかを判定する場合、 ::の左側の文字列には追加で self , parentが許可され、 ::の右側の文字列には追加で protected , privateなメソッドの名前が許可されます。

<?phpclassParentClass{publicstaticfunctionsomePublicClassMethod(){}publicfunctionsomePublicObjectMethod(){}}classSomeClassextendsParentClass{publicstaticfunctionclassMethod(){var_dump(is_callable('SomeClass::somePublicClassMethod'));// => bool(true)var_dump(is_callable('self::somePublicClassMethod'));// => bool(true)var_dump(is_callable('parent::somePublicClassMethod'));// => bool(true)var_dump(is_callable('SomeClass::someProtectedClassMethod'));// => bool(true)var_dump(is_callable('SomeClass::somePrivateClassMethod'));// => bool(true)var_dump(is_callable('SomeClass::somePublicObjectMethod'));// => bool(true)var_dump(is_callable('self::somePublicObjectMethod'));// => bool(true)var_dump(is_callable('parent::somePublicObjectMethod'));// => bool(true)var_dump(is_callable('SomeClass::someProtectedObjectMethod'));// => bool(true)var_dump(is_callable('SomeClass::somePrivateObjectMethod'));// => bool(true)}publicstaticfunctionsomePublicClassMethod(){}protectedstaticfunctionsomeProtectedClassMethod(){}privatestaticfunctionsomePrivateClassMethod(){}publicfunctionobjectMethod(){var_dump(is_callable('SomeClass::somePublicClassMethod'));// => bool(true)var_dump(is_callable('self::somePublicClassMethod'));// => bool(true)var_dump(is_callable('parent::somePublicClassMethod'));// => bool(true)var_dump(is_callable('SomeClass::someProtectedClassMethod'));// => bool(true)var_dump(is_callable('SomeClass::somePrivateClassMethod'));// => bool(true)var_dump(is_callable('SomeClass::somePublicObjectMethod'));// => bool(true)var_dump(is_callable('self::somePublicObjectMethod'));// => bool(true)var_dump(is_callable('parent::somePublicObjectMethod'));// => bool(true)var_dump(is_callable('SomeClass::someProtectedObjectMethod'));// => bool(true)var_dump(is_callable('SomeClass::somePrivateObjectMethod'));// => bool(true)}publicfunctionsomePublicObjectMethod(){}protectedfunctionsomeProtectedObjectMethod(){}privatefunctionsomePrivateObjectMethod(){}}SomeClass::classMethod();(newSomeClass())->objectMethod();

Closureオブジェクト

無名関数式を使って作成した Closureオブジェクトは Callableと判定されます。

<?phpvar_dump(is_callable(function():bool{returntrue;}));// => bool(true)

__invoke()を実装したオブジェクト

public function __invoke()を実装したオブジェクトは Callableと判定されます。

<?phpvar_dump(is_callable(newclass(){publicfunction__invoke():bool{returntrue;}}));// => bool(true)

[クラス名, ::を含まない文字列]

※力尽きたので一旦記事は公開する

[クラス名, ::の両側になんかある文字列]

※力尽きたので一旦記事は公開する

[オブジェクト, ::を含まない文字列]

※力尽きたので一旦記事は公開する

[オブジェクト, ::の両側になんかある文字列]

※力尽きたので一旦記事は公開する

MacOSをCatalinaにしてphpが動作しなくなったら

MacOSのバージョンアップ時の定期事故である、phpenvの再インストール

phpが動かなくなった時の対応

よくmacosのバージョンをあげたらいきなりphpが動かなくなることが多いです。
今まで毎回そうだったのでもう定期事故として考えています。面倒だけど、仕方なく新しいの使いたいから今回もバージョンアップ!

$ php -v
dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.64.dylib
  Referenced from: /Users/syokatsu/.phpenv/versions/7.3.13/bin/php
  Reason: image not found
zsh: abort      php -v

やはりこれか〜!
icu4cの問題は以前もあったので、気軽に次のステップへ

まずはphp-buildのバージョンアップから!

php-buildのバージョンアップ

$ cd ~/.phpenv/plugins/php-build
$ git pull

今まではphp7.3系を利用していたが、php7.4が落ちてきたのでそろそろphp7.4系をinstallすることに!

php7.4のinstall

関連libraryインストール。必要なのは色々あるはずなので、必要な物をinstall

$ brew install bzip2 icu4c krb5 libedit libzip oniguruma openssl@1.1 pkg-config tidy-html5
$ vi ~/.phpenv/plugins/php-build/share/php-build/default_configure_options
~~
この内容を追加
--with-zlib-dir=/usr/local/opt/zlib
--with-bz2=/usr/local/opt/bzip2
--with-libedit=/usr/local/opt/libedit
~~

$ vi ~/.zshrc
export PKG_CONFIG_PATH="/usr/local/opt/krb5/lib/pkgconfig:/usr/local/opt/icu4c/lib/pkgconfig:/usr/local/opt/libedit/lib/pkgconfig:/usr/local/opt/libjpeg/lib/pkgconfig:/usr/local/opt/libpng/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig:/usr/local/opt/libzip/lib/pkgconfig:/usr/local/opt/oniguruma/lib/pkgconfig:/usr/local/opt/openssl@1.1/lib/pkgconfig:/usr/local/opt/tidy-html5/lib/pkgconfig" 

phpのinstall

$ phpenv install 7.4.6
...
[Success]: Built 7.4.6 successfully.

$ phpenv global 7.4.6
$ php -v
PHP 7.4.6 (cli) (built: May 23 2020 01:07:57) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.6, Copyright (c), by Zend Technologies
    with Xdebug v2.9.5, Copyright (c) 2002-2020, by Derick Rethans

Laravel CSVの出力処理を実装する

概要

Laravelを使って、CSVファイルを出力するサンプルを作成します。

背景

データベースやファイルアクセスをしないテストの方法、インターフェースやジェネレータを使ったコードの書き方等、個別に詳しく書かれた記事はあれど実際使うにはどう書き始めたらいいのかベストプラクティスがわかりませんでした。

日々の業務や副業、勉強会を通じてようやく自分の中で少しずつイメージができてきたので現時点で最高のアウトプットをしていこうと思いました。

目的

この記事ではテストを意識したコードかつ、シンプルに書くことを目標にしてます。
より良いコードにしたいのでアドバイスもらえたらうれしいです。

説明不足なところがあったら補足を追記するので気軽に質問等もいただけたら嬉しいです。

環境

  • PHP 7.4.1
  • Laravel 6.14.0
  • MySQL 8.0.19

サンプルコード

https://github.com/ucan-lab/learn-laravel-export-csv

$git clone git@github.com:ucan-lab/learn-laravel-export-csv.git
$cd learn-laravel-export-csv
$make install$make app
$php artisan migrate:fresh --seed#csv出力コマンド
$php artisan export:user
#テスト実行
$./vendor/bin/phpunit

今回のゴール

スクリーンショット 2020-02-20 12.18.33.png

usersテーブルに入ってるデータをcsv出力する処理を作るところまでゴールとします。

名前,メールアドレス,作成日,更新日
PROF. RACHELLE KUHIC I,leola.rath@example.com,2020-02-01 23:59:59,2020-02-01 23:59:59
JAYLON WOLF,osinski.fernando@example.net,2020-02-01 23:59:59,2020-02-01 23:59:59
LELAND DECKOW,bokon@example.org,2020-02-01 23:59:59,2020-02-01 23:59:59

名前の列は大文字に変換して出力する仕様です。

ベースのコード

環境はこちらのコードを丸コピしてます。

追加したファイル一覧

https://github.com/ucan-lab/learn-laravel-export-csv/pull/1

src/app/Console/Commands/ExportUserCommand.php
src/app/Domain/UserRow.php
src/app/Domain/UserRowHeader.php
src/app/Http/Controllers/Auth/RegisterController.php
src/app/Infrastructure/Adapter/DbUserRepository.php
src/app/Infrastructure/Adapter/FileUserCsvExport.php
src/app/Infrastructure/Adapter/InMemoryUserCsvExport.php
src/app/Infrastructure/Adapter/InMemoryUserRepository.php
src/app/Infrastructure/Eloquent/User.php
src/app/Infrastructure/Port/Export.php
src/app/Infrastructure/Port/UserRepository.php
src/app/Providers/AppServiceProvider.php
src/app/UseCase/UserCsvExportUseCase.php
src/database/factories/UserFactory.php
src/database/seeds/DatabaseSeeder.php
src/database/seeds/UsersTableSeeder.php
src/tests/Unit/UserCsvExportUseCaseTest.php

マイグレーション(テーブル)の確認

今回はLaravelが元々用意してくれている users テーブルをそのまま使います。

src/database/migrations/2014_10_12_000000_create_users_table.php
<?phpuseIlluminate\Database\Migrations\Migration;useIlluminate\Database\Schema\Blueprint;useIlluminate\Support\Facades\Schema;classCreateUsersTableextendsMigration{/**
     * Run the migrations.
     *
     * @return void
     */publicfunctionup(){Schema::create('users',function(Blueprint$table){$table->bigIncrements('id');$table->string('name');$table->string('email')->unique();$table->timestamp('email_verified_at')->nullable();$table->string('password');$table->rememberToken();$table->timestamps();});}/**
     * Reverse the migrations.
     *
     * @return void
     */publicfunctiondown(){Schema::dropIfExists('users');}}

MySQLのテーブル定義も確認しておきます。

$make mysql
mysql>desc users;+-------------------+-----------------+------+-----+---------+----------------+
| Field             | Type            | Null | Key | Default | Extra          |
+-------------------+-----------------+------+-----+---------+----------------+
| id                | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)    | NO   |     | NULL    |                |
| email             | varchar(255)    | NO   | UNI | NULL    |                |
| email_verified_at | timestamp       | YES  |     | NULL    |                |
| password          | varchar(255)    | NO   |     | NULL    |                |
| remember_token    | varchar(100)    | YES  |     | NULL    |                |
| created_at        | timestamp       | YES  |     | NULL    |                |
| updated_at        | timestamp       | YES  |     | NULL    |                |
+-------------------+-----------------+------+-----+---------+----------------+

app/User.php => app/Infrastructure/Eloquent/User.php

LaravelのEloquentモデルはデフォルトだとapp直下に配置されます。
app/Infrastructure/Eloquent/User.phpへ移動します。
依存するファイルも合わせて修正します。詳細はコミットログ参照

シーダーの作成

Laravelには、シーディングモデルファクトリFakerが用意されており、ダミーデータを簡単に作成できます。

$php artisan make:seeder UsersTableSeeder

src/database/seeds/UsersTableSeeder.phpシーダーのひな形クラスを作ってくれるので下記のように追記します。

src/database/seeds/UsersTableSeeder.php
<?phpdeclare(strict_types=1);useApp\Infrastructure\Eloquent\User;useIlluminate\Database\Seeder;classUsersTableSeederextendsSeeder{/**
     * Run the database seeds.
     *
     * @return void
     */publicfunctionrun(){factory(User::class,3)->create();}}

上記の用にシーダーを追加するだけで、テストデータを3件作成してくれます。
Userモデルクラスの各プロパティにどんなデータが入るかの定義はモデルファクトリで定義されてます。

src/database/factories/UserFactory.php
<?phpdeclare(strict_types=1);/** @var \Illuminate\Database\Eloquent\Factory $factory */useApp\Infrastructure\Eloquent\User;useFaker\GeneratorasFaker;useIlluminate\Support\Str;/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/$factory->define(User::class,function(Faker$faker){return['name'=>$faker->name,'email'=>$faker->unique()->safeEmail,'email_verified_at'=>now(),'password'=>'$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',// password'remember_token'=>Str::random(10),];});

元々用意されている src/database/seeds/DatabaseSeeder.phpUsersTableSeederを呼び出す記述を追記します。

src/database/seeds/DatabaseSeeder.php
<?phpdeclare(strict_types=1);useIlluminate\Database\Seeder;classDatabaseSeederextendsSeeder{/**
     * Seed the application's database.
     *
     * @return void
     */publicfunctionrun(){$this->call(UsersTableSeeder::class);}}

UserRow, UserRowHeader ドメインを定義

この辺りから本題です。

App\Domain\UserRowHeader

app/Domain/UserRowHeader.php
<?phpdeclare(strict_types=1);namespaceApp\Domain;finalclassUserRowHeader{privateconstEOF="\n";privateconstHEADER=['名前','メールアドレス','作成日','更新日',];publicstaticfunctiontoCsv():string{returnimplode(',',self::HEADER).self::EOF;}}

UserRowHeader ドメインクラスではCSVのヘッダー行となる1行目の定義をしてます。

App\Domain\UserRow

app/Domain/UserRow.php
<?phpdeclare(strict_types=1);namespaceApp\Domain;useCarbon\Carbon;finalclassUserRow{privateconstEOF="\n";privateconstDATE_FORMAT='Y-m-d H:i:s';privatestring$name;privatestring$email;privateCarbon$createdAt;privateCarbon$updatedAt;publicfunction__construct(string$name,string$email,Carbon$createdAt,Carbon$updatedAt){$this->name=$name;$this->email=$email;$this->createdAt=$createdAt;$this->updatedAt=$updatedAt;}/**
     * @return string
     */publicfunctiontoCsv():string{returnimplode(',',$this->toArray()).self::EOF;}/**
     * @return array
     */privatefunctiontoArray():array{return[$this->getName(),$this->email,$this->createdAt->format(self::DATE_FORMAT),$this->updatedAt->format(self::DATE_FORMAT),];}/**
     * @return string
     */privatefunctiongetName():string{returnstrtoupper($this->name);}}

UserRow ドメインクラスではCSVの1行の定義をしてます。
ユーザー名は大文字や日付のフォーマット等の業務ロジックはここにまとめます。

UserCsvExportUseCase を定義

app/Infrastructure/Export.php
<?phpdeclare(strict_types=1);namespaceApp\Infrastructure\Port;interfaceExport{publicfunctionprepare(string$header):void;publicfunctionwrite(string$row):void;publicfunctiondisorganize():void;}

Export インターフェースを継承するクラスは prepare(前処理)、write(書き込み)、disorganize(後処理)のメソッドを契約します。

app/Infrastructure/UserRepository.php
<?phpdeclare(strict_types=1);namespaceApp\Infrastructure\Port;useGenerator;interfaceUserRepository{publicfunctionfindAll():Generator;}

UserRepository インターフェースを継承するクラスはfindAll(全件取得)のメソッドを契約します。

app/UseCase/UserCsvExportUseCase.php
<?phpdeclare(strict_types=1);namespaceApp\UseCase;useApp\Domain\UserRow;useApp\Domain\UserRowHeader;useApp\Infrastructure\Port\Export;useApp\Infrastructure\Port\UserRepository;finalclassUserCsvExportUseCase{/**
     * @var UserRepository
     */privateUserRepository$repository;/**
     * @var Export
     */privateExport$export;/**
     * @param UserRepository $repository
     * @param Export $export
     */publicfunction__construct(UserRepository$repository,Export$export){$this->repository=$repository;$this->export=$export;}/**
     * @return void
     */publicfunctionhandle():void{$this->export->prepare(UserRowHeader::toCsv());/** @var UserRow $row */foreach($this->repository->findAll()as$row){$this->export->write($row->toCsv());}$this->export->disorganize();}}

UserCsvExportUseCase ユースケースクラスはUserRepositoryとExportインターフェースに依存します。
前処理して、全件取得して、書き込みして、後処理して終わるシンプルな作りにできました。
各インターフェースを契約するクラスの中身はあとで書きます。

ユースケースのテストを書く

テストを書く際、データベースやファイルに直接読み書きするようなテストを書いてしまうと
最初は問題ないですが、テストが増えるにつれてテストの実行速度がどんどん落ちてしまいます。

そのため、テストを書く際はデータベースやファイルアクセスが発生しないようメモリ内で良い感じのテストを書きます。

InMemoryUserCsvExport

app/Infrastructure/Adapter/InMemoryUserCsvExport.php
<?phpdeclare(strict_types=1);namespaceApp\Infrastructure\Adapter;useApp\Infrastructure\Port\Export;finalclassInMemoryUserCsvExportimplementsExport{/**
     * @var string
     */publicstring$file;/**
     * @param string $header
     */publicfunctionprepare(string$header):void{$this->file=$header;}/**
     * @param string $row
     */publicfunctionwrite(string$row):void{$this->file.=$row;}/**
     * @return void
     */publicfunctiondisorganize():void{}}

Exportインターフェースを契約したInMemoryUserCsvExportクラスを実装します。
やってることは簡単で、prepareメソッドで$fileプロパティに文字列を入れてwriteメソッドが呼ばれたらどんどん追記する形です。
実際にファイルアクセスする場合はdisorganizefclose等の処理を入れますが、ファイルアクセスしないので関数だけ定義してます。

InMemoryUserRepository

app/Infrastructure/Adapter/InMemoryUserRepository.php
<?phpdeclare(strict_types=1);namespaceApp\Infrastructure\Adapter;useApp\Domain\UserRow;useApp\Infrastructure\Eloquent\User;useApp\Infrastructure\Port\UserRepository;useGenerator;finalclassInMemoryUserRepositoryimplementsUserRepository{privatearray$usersAttributes;/**
     * @param array $users
     */publicfunction__construct(array$users){$this->usersAttributes=$users;}/**
     * @return Generator
     */publicfunctionfindAll():Generator{foreach($this->usersAttributesas$userAttributes){yield$this->makeUserRow(factory(User::class)->make($userAttributes));}}/**
     * @param User $user
     * @return UserRow
     */privatefunctionmakeUserRow(User$user):UserRow{returnnewUserRow($user->name,$user->email,$user->created_at,$user->updated_at);}}

補足: ジェネレータ

findAllの戻り値の型としてGeneratorオブジェクトを返すと呼び出した側はforeachを使って順に呼び出すことができます。
returnではなくyieldを指定します。
yieldUserRowのインスタンスを返してます。

// UserCsvExportUseCase で findAll を foreach でループ処理できます。foreach($this->repository->findAll()as$row){$this->export->write($row->toCsv());}

ジェネレータのメリットはforeachでループ処理するために巨大な配列を持つ必要がなく1件処理が終わったらメモリを解放して次の処理を実行してくれるので、バッチ処理等のメモリをたくさん使いそうな場合に効果を発揮します。

UserCsvExportUseCaseTest

先ほど作成したInMemoryUserRepositoryInMemoryUserCsvExportを使ってテストコードを書きます。

tests/Unit/UserCsvExportUseCaseTest.php
<?phpdeclare(strict_types=1);namespaceTests\Unit;useApp\Infrastructure\Adapter\InMemoryUserCsvExport;useApp\Infrastructure\Adapter\InMemoryUserRepository;useApp\UseCase\UserCsvExportUseCase;useTests\TestCase;finalclassUserCsvExportUseCaseTestextendsTestCase{/**
     * @param array $users
     * @param string $expectedCsv
     * @dataProvider dataResolve
     */publicfunctiontestResolve(array$users,string$expectedCsv):void{$repository=newInMemoryUserRepository($users);$export=newInMemoryUserCsvExport();$useCase=newUserCsvExportUseCase($repository,$export);$useCase->handle();$this->assertEquals($expectedCsv,$export->file);}/**
     * @return array
     */publicfunctiondataResolve():array{return['正常3件'=>$this->case正常3件(),'正常0件'=>$this->case正常0件(),];}/**
     * @return array
     */publicfunctioncase正常3件():array{$usersAttributes=[['name'=>'yamada','email'=>'yamada@example.com','created_at'=>'2020-01-01 00:00:00','updated_at'=>'2020-01-01 00:00:00'],['name'=>'suzuki','email'=>'suzuki@example.com','created_at'=>'2020-01-01 00:00:00','updated_at'=>'2020-01-01 00:00:00'],['name'=>'tanaka','email'=>'tanaka@example.com','created_at'=>'2020-01-01 00:00:00','updated_at'=>'2020-01-01 00:00:00'],];$expectedCsv=<<<EOT名前,メールアドレス,作成日,更新日YAMADA,yamada@example.com,2020-01-0100:00:00,2020-01-0100:00:00SUZUKI,suzuki@example.com,2020-01-0100:00:00,2020-01-0100:00:00TANAKA,tanaka@example.com,2020-01-0100:00:00,2020-01-0100:00:00EOT;return[$usersAttributes,$expectedCsv,];}/**
     * @return array
     */publicfunctioncase正常0件():array{$usersAttributes=[];$expectedCsv=<<<EOT名前,メールアドレス,作成日,更新日EOT;return[$usersAttributes,$expectedCsv,];}}

想定している $expectedCsvの値とユースケースを実行して作成された値 $export->fileが一致すればokです。

補足: dataProvider

PHPUnitのdataProviderについて補足です。
PHPUnitを実行する際に --debugオプションを付けると詳細ログが見れます。
dataProviderで作った引数もログに出てくるので分かりやすくなります。

データプロバイダ | phpunit.readthedocs.io

$./vendor/bin/phpunit --debugPHPUnit 8.5.2 by Sebastian Bergmann and contributors.

Test 'Tests\Unit\ExampleTest::testBasicTest' started
Test 'Tests\Unit\ExampleTest::testBasicTest' ended
Test 'Tests\Unit\UserCsvExportUseCaseTest::testResolve with data set "正常3件" (array(array('yamada', 'yamada@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00'), array('suzuki', 'suzuki@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00'), array('tanaka', 'tanaka@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00')), '名前,メールアドレス,作成日,更新日\nYAMADA,ya...0:00\n')' started
Test 'Tests\Unit\UserCsvExportUseCaseTest::testResolve with data set "正常3件" (array(array('yamada', 'yamada@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00'), array('suzuki', 'suzuki@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00'), array('tanaka', 'tanaka@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00')), '名前,メールアドレス,作成日,更新日\nYAMADA,ya...0:00\n')' ended
Test 'Tests\Unit\UserCsvExportUseCaseTest::testResolve with data set "正常0件" (array(), '名前,メールアドレス,作成日,更新日\n')' started
Test 'Tests\Unit\UserCsvExportUseCaseTest::testResolve with data set "正常0件" (array(), '名前,メールアドレス,作成日,更新日\n')' ended
Test 'Tests\Feature\ExampleTest::testBasicTest' started
Test 'Tests\Feature\ExampleTest::testBasicTest' ended


Time: 3.04 seconds, Memory: 20.00 MB

OK (4 tests, 4 assertions)

CSVの出力処理を実装する

テストコードが書けたところで、実際にデータベースから取得してCSVファイルを出力する処理を実装します。

DbUserRepository

app/Infrastructure/Adapter/DbUserRepository.php
<?phpdeclare(strict_types=1);namespaceApp\Infrastructure\Adapter;useApp\Domain\UserRow;useApp\Infrastructure\Eloquent\User;useApp\Infrastructure\Port\UserRepository;useGenerator;finalclassDbUserRepositoryimplementsUserRepository{/**
     * @return Generator
     */publicfunctionfindAll():Generator{/** @var User $user */foreach(User::query()->cursor()as$user){yieldnewUserRow($user->name,$user->email,$user->created_at,$user->updated_at);}}}

UserRepositoryを契約したDbUserRepositoryクラスです。

補足: User::query()->cursor()

cursor()を使うとPDOStatement::fetch
結果セットから1行ずつ取得できます。 cursor()の返り値もジェネレータオブジェクトになります。

User::query()->cursor()ではなく User::all()も動作するかと思いますが、一度に大量のデータを取得するのでデータ件数によってはメモリオーバーになってしまう懸念があります。

FileUserCsvExport

app/Infrastructure/Adapter/FileUserCsvExport.php
<?phpdeclare(strict_types=1);namespaceApp\Infrastructure\Adapter;useApp\Infrastructure\Port\Export;finalclassFileUserCsvExportimplementsExport{/**
     * @var string
     */privatestring$streamFilePath;/**
     * @var resource
     */private$handle;/**
     * @param string $header
     * @return void
     */publicfunctionprepare(string$header):void{$this->streamFilePath=$this->makeStreamFile();$this->handle=fopen($this->streamFilePath,'wb+');$this->write($header);}/**
     * @param string $row
     * @return void
     */publicfunctionwrite(string$row):void{fwrite($this->handle,$row);}/**
     * @return void
     */publicfunctiondisorganize():void{fclose($this->handle);// 後処理 配置したい場所へコピーする等dump(file_get_contents($this->streamFilePath));unlink($this->streamFilePath);}/**
     * @return string
     */privatefunctionmakeStreamFile():string{returntempnam(sys_get_temp_dir(),config('app.name'));}}

Exportを契約したFileUserCsvExportクラスです。

ExportUserCommand

app/Console/Commands/ExportUserCommand.php
<?phpdeclare(strict_types=1);namespaceApp\Console\Commands;useApp\UseCase\UserCsvExportUseCase;useIlluminate\Console\Command;classExportUserCommandextendsCommand{/**
     * The name and signature of the console command.
     *
     * @var string
     */protected$signature='export:user';/**
     * The console command description.
     *
     * @var string
     */protected$description='export user data.';/**
     * @var UserCsvExportUseCase
     */privateUserCsvExportUseCase$useCase;/**
     * ExportUserCommand constructor.
     * @param UserCsvExportUseCase $useCase
     */publicfunction__construct(UserCsvExportUseCase$useCase){parent::__construct();$this->useCase=$useCase;}/**
     * @return void
     */publicfunctionhandle():void{$this->useCase->handle();}}

LaravelにはArtisanコンソールというコマンドラインインターフェイスが用意されてます。
コマンドクラスを作るだけで簡単に自作コマンドを追加できます。

ExportUserCommandでやってることは、UserCsvExportUseCaseのインスタンスを受け取って、handleメソッドを呼び出すだけです。

$php artisan export:user

上記のコマンドが追加されます。

依存性の注入(DI)

app/Providers/AppServiceProvider.php
<?phpdeclare(strict_types=1);namespaceApp\Providers;useApp\Infrastructure\Adapter\DbUserRepository;useApp\Infrastructure\Adapter\FileUserCsvExport;useApp\UseCase\UserCsvExportUseCase;useIlluminate\Support\ServiceProvider;classAppServiceProviderextendsServiceProvider{/**
     * Register any application services.
     *
     * @return void
     */publicfunctionregister(){$this->app->bind(UserCsvExportUseCase::class,function($app){returnnewUserCsvExportUseCase(newDbUserRepository(),newFileUserCsvExport());});}/**
     * Bootstrap any application services.
     *
     * @return void
     */publicfunctionboot(){//}}

UserCsvExportUseCaseクラスをサービスコンテナに登録します。
ここで登録しているので、ExportUserCommandはコンストラクタインジェクションでUserCsvExportUseCaseクラスのインスタンスを受け取れます。

Vultr VPSのCentOS8でNginx+PHP7.4+MySQL8でWordPressをインストール

はじめに

WordPressのインストール記事は多く見つかるが、LEMP Stack (Linux, Nginx, MySQL, PHP) を使わずに個別インストールした手順をメモしておきます。Vultrには最初からWordPressが入ったVPSも選べますが、CentOS7ベースなのでCentOS8を使いたい場合は自分でインストールする必要があります。

私の知る限り、Vultrより安いWordPressが動くVPSは無かったです。Vultrはキャンペーンで紹介者のリンク経由で新規申込した場合に$100分のクレジットがもらえますので、是非使ってください。$100あれば複数VPSを立てたり、クラスタ構成を試したり、いろいろできます。紹介者にもちょっとだけクレジットが入ります。

このリンクで$100もらえます

スクリーンショット 2020-03-09 午後7.03.25.png

私もリファラー経由でアカウントを作ったので$100もらえました。これで最初にあれやこれや試すのは無料でできます。
スクリーンショット 2020-03-09 午後7.17.41.png

1. CentOS8をVultr VPSで構成

とりあえずのお試しなので、一番安いサーバーを選択した。GB単価の安いBlock StorageにWordPressの画像を入れるかも知れないので、Block StorageのチェックをON。別記事で使ったSSH鍵も使えるようにしておくとSSH接続が楽です。なお、画像ファイルが250GBを超える事が想定される場合は最初からVultr Object Storageを使った方がストレージ費用が安くなる(250GB/$5から)と思いますが、その規模ならこの記事の対象読者にならないでしょうからVultr Object Storageは割愛します。

(1) VPS作成

my.vultr.com_deploy_(iPad Pro).png

MacからSSH接続
$ssh-i~/.ssh/vultr2root@VultrIPアドレスTheauthenticityofhost'IPアドレス (IPアドレス)'can't be established.
ECDSA key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'IPアドレス' (ECDSA) to the list of known hosts.
root@IPアドレス'spassword: 
Activatethewebconsolewith: systemctlenable--nowcockpit.socket

(2) OSの構成

私の過去記事の5〜6をやってSwap領域やFirewallを構成しておく。

過去記事
Vultr VPSにCentOS7, Ruby on Rails 6, Puma, Capistrano3でのProduction環境デプロイ

2. Nginxインストール

(1) yumでインストール

# yum update
# yum info nginx
Last metadata expiration check: 0:00:58 ago on Mon 01 Jun 2020 02:22:09 AM UTC.
Available Packages
Name         : nginx
Epoch        : 1
Version      : 1.14.1
Release      : 9.module_el8.0.0+184+e34fea82
Architecture : x86_64
Size         : 570 k
Source       : nginx-1.14.1-9.module_el8.0.0+184+e34fea82.src.rpm
Repository   : AppStream
Summary      : A high performance web server and reverse proxy server
URL          : http://nginx.org/
License      : BSD
Description  : Nginx is a web server and a reverse proxy server for HTTP, SMTP, POP3 and
             : IMAP protocols, with a strong focus on high concurrency, performance and low
             : memory usage.

# yum install nginx

(2) Start Nginx on Centos 8

起動
# systemctl start nginx
有効化
# systemctl enable nginxCreatedsymlink/etc/systemd/system/multi-user.target.wants/nginx.service/usr/lib/systemd/system/nginx.service.
ステータス確認
# systemctl status nginxnginx.service-ThenginxHTTPandreverseproxyserverLoaded:loaded(/usr/lib/systemd/system/nginx.service;enabled;vendorpreset: disabled)Active:active(running)sinceMon2020-06-0102:31:37UTC;1min23sagoMainPID:2820(nginx)Tasks:2(limit: 2864)Memory:7.3MCGroup:/system.slice/nginx.service├─2820nginx: masterprocess/usr/sbin/nginx└─2821nginx: workerprocessJun0102:31:36vultrguestsystemd[1]:StartingThenginxHTTPandreverseproxyserver...Jun0102:31:36vultrguestnginx[2816]:nginx: theconfigurationfile/etc/nginx/nginx.confsyntaxisokJun0102:31:36vultrguestnginx[2816]:nginx: configurationfile/etc/nginx/nginx.conftestissuccessfulJun0102:31:37vultrguestsystemd[1]:nginx.service:FailedtoparsePIDfromfile/run/nginx.pid:InvalidargumentJun0102:31:37vultrguestsystemd[1]:StartedThenginxHTTPandreverseproxyserver.

一応、基本的なコマンドを書いておきます。OSのバージョンとかで少し違うので。

停止
# systemctl stop nginx
再起動
# systemctl restart nginx
Config再読込
# systemctl reload nginx

NginxはDefault構成でも動作確認できるので、ブラウザにVultr VPSのIPアドレスを入れて確認します。

スクリーンショット 2020-05-31 午後10.37.21.png

(3) Nginxの構成

構成ファイルの場所

  • Nginx configuration directory: /etc/nginx
  • Nginx root directory: /usr/share/nginx/html
  • Master/Global configuration file: /etc/nginx/nginx.conf

3. PHP 7.4のインストール

(1) EPEL と Remi リポジトリの追加

dnfコマンドでリポジトリを追加します。

EPEL
# dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm# rpm -qa | grep epelepel-release-8-8.el8.noarch
REMI
# dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm# rpm -qa | grep remiremi-release-8.1-2.el8.remi.noarch

(2) PHPのインストール

利用可能なPHPモジュールを確認します。

# dnf module list php
Remi's Modular repository for Enterprise Linux 8 - x86_64                                                                                              474 kB/s | 569 kB     00:01    
Safe Remi's RPM repository for Enterprise Linux 8 - x86_64                                                                                             982 kB/s | 1.5 MB     00:01    
CentOS-8 - AppStream
Name                               Stream                                 Profiles                                                 Summary                                             
php                                7.2 [d]                                common [d], devel, minimal                               PHP scripting language                              
php                                7.3                                    common, devel, minimal                                   PHP scripting language                              

Remi's Modular repository for Enterprise Linux 8 - x86_64
Name                               Stream                                 Profiles                                                 Summary                                             
php                                remi-7.2                               common [d], devel, minimal                               PHP scripting language                              
php                                remi-7.3                               common [d], devel, minimal                               PHP scripting language                              
php                                remi-7.4                               common [d], devel, minimal                               PHP scripting language                              

Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled

remi-7.4が最新なので、有効化します。

# dnf module enable php:remi-7.4
Last metadata expiration check: 0:01:40 ago on Mon 01 Jun 2020 02:48:32 AM UTC.
Dependencies resolved.
=======================================================================================================================================================================================
 Package                                     Architecture                               Version                                      Repository                                   Size
=======================================================================================================================================================================================
Enabling module streams:
 php                                                                                    remi-7.4                                                                                      

Transaction Summary
=======================================================================================================================================================================================

Is this ok [y/N]: y
Complete!

PHP remi-7.4モジュールを有効化したら、PHPと関連パッケージをインストールします。

# dnf install php php-cli php-common
Last metadata expiration check: 0:03:08 ago on Mon 01 Jun 2020 02:48:32 AM UTC.
Dependencies resolved.
=======================================================================================================================================================================================
 Package                                    Architecture                   Version                                                          Repository                            Size
=======================================================================================================================================================================================
Installing:
 php                                        x86_64                         7.4.6-1.el8.remi                                                 remi-modular                         3.0 M
 php-cli                                    x86_64                         7.4.6-1.el8.remi                                                 remi-modular                         4.6 M
 php-common                                 x86_64                         7.4.6-1.el8.remi                                                 remi-modular                         1.2 M
Installing dependencies:
 apr                                        x86_64                         1.6.3-9.el8                                                      AppStream                            125 k
 apr-util                                   x86_64                         1.6.1-6.el8                                                      AppStream                            105 k
 centos-logos-httpd                         noarch                         80.5-2.el8                                                       AppStream                             24 k
 httpd                                      x86_64                         2.4.37-16.module_el8.1.0+256+ae790463                            AppStream                            1.7 M
 httpd-filesystem                           noarch                         2.4.37-16.module_el8.1.0+256+ae790463                            AppStream                             35 k
 httpd-tools                                x86_64                         2.4.37-16.module_el8.1.0+256+ae790463                            AppStream                            103 k
 mod_http2                                  x86_64                         1.11.3-3.module_el8.1.0+213+acce2796                             AppStream                            158 k
 oniguruma                                  x86_64                         6.8.2-1.el8                                                      AppStream                            188 k
 libsodium                                  x86_64                         1.0.18-2.el8                                                     epel                                 162 k
 php-json                                   x86_64                         7.4.6-1.el8.remi                                                 remi-modular                          74 k
Installing weak dependencies:
 apr-util-bdb                               x86_64                         1.6.1-6.el8                                                      AppStream                             25 k
 apr-util-openssl                           x86_64                         1.6.1-6.el8                                                      AppStream                             27 k
 php-fpm                                    x86_64                         7.4.6-1.el8.remi                                                 remi-modular                         1.6 M
 php-mbstring                               x86_64                         7.4.6-1.el8.remi                                                 remi-modular                         527 k
 php-opcache                                x86_64                         7.4.6-1.el8.remi                                                 remi-modular                         334 k
 php-pdo                                    x86_64                         7.4.6-1.el8.remi                                                 remi-modular                         143 k
 php-sodium                                 x86_64                         7.4.6-1.el8.remi                                                 remi-modular                          87 k
 php-xml                                    x86_64                         7.4.6-1.el8.remi                                                 remi-modular                         215 k
Enabling module streams:
 httpd                                                                     2.4                                                                                                        

Transaction Summary
=======================================================================================================================================================================================
Install  21 Packages

Total download size: 14 M
Installed size: 65 M
Is this ok [y/N]: y

インストールしたPHPとPHP-FPMのバージョンを確認します。2020年6月時点で最新の7.4が入りました。

# php -v
PHP 7.4.6 (cli) (built: May 12 2020 08:09:15) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.6, Copyright (c), by Zend Technologies

# php-fpm -v
PHP 7.4.6 (fpm-fcgi) (built: May 12 2020 08:09:15)
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.6, Copyright (c), by Zend Technologies

(3) 最大ファイルサイズ設定の変更

DefaultではPHPは2MBまでのファイルしかアップロードできないので、より大きなファイルをアップロードできりょうにphp.iniupload_max_filesizeおよびnginx.confを変更します。

/etc/php.ini
;Maximumallowedsizeforuploadedfiles.;http://php.net/upload-max-filesizeupload_max_filesize=256M;MaximumsizeofPOSTdatathatPHPwillaccept.;Itsvaluemaybe0todisablethelimit.ItisignoredifPOSTdatareading;isdisabledthroughenable_post_data_reading.;http://php.net/post-max-sizepost_max_size=256M
/etc/nginx/nginx.conf
http{....client_max_body_size256M;....}
php-fpmサービスとnginxを再起動
# systemctl restart php-fpm# systemctl restart nginx

(4) NginxとPHPの連携の確認

phpinfo();がブラウザでアクセスして見えるように構成します。

index.phpファイル準備
# echo "<?php phpinfo(); ?>" > /var/www/html/index.php# chown nginx.nginx /var/www/html/index.php

何も構成しない状態でブラウザアクセスすると、エラーになりました。

スクリーンショット 2020-05-31 午後11.15.25.png

nginx-PHP連携のソケットを確認します。

# cat /etc/nginx/conf.d/php-fpm.conf
# PHP-FPM FastCGI server
# network or unix domain socket configuration

upstream php-fpm {
        server unix:/run/php-fpm/www.sock; これをメモしておく。
}
/etc/nginx/conf.d/wordpress.conf
server{server_nameVPSIPアドレス;root/var/www/html;location/{indexindex.htmlindex.htmindex.php;}location~\.php${include/etc/nginx/fastcgi_params;fastcgi_passunix:/run/php-fpm/www.sock; ここはさっき確認したソケットのフルパスfastcgi_indexindex.php;fastcgi_paramSCRIPT_FILENAME$document_root$fastcgi_script_name;}}

/etc/nginx/conf.d/wordpress.confの変更をnginxに読み込みます。

# systemctl reload nginx

php-fpmの/etc/php-fpm.d/www.conf設定ファイルでユーザーとグループをnginxに変更します。

/etc/php-fpm.d/www.conf
-user=apache-group=apache+user=nginx+group=nginx

/etc/php-fpm.d/www.confの変更を反映させるため、php-fpmを再起動します。

# systemctl restart php-fpm

設定したら、index.phpが読めるかどうか、ブラウザで確認します。以下のような画面が出れば正しく設定できています。

スクリーンショット 2020-05-31 午後11.29.29.png

4. MySQL 8のインストール

(1) MariaDBをアンインストール

MariaDBをインストールしてしまっていたら、事前にアンインストールします。

MariaDBを確認
# dnf list installed | grep mariadbmariadb.x86_643:10.3.11-2.module_el8.0.0+35+6f2527edmariadb-common.x86_643:10.3.11-2.module_el8.0.0+35+6f2527ed
アンインストール
# dnf remove mariadb

(2) MySQL8.0のインストール

インストールできるバージョンを確認します。

インストールできるバージョンを確認
# dnf info mysqlLastmetadataexpirationcheck: 0:09:51agoonMon01Jun202002:48:32AMUTC.AvailablePackagesName:mysqlVersion:8.0.17Release:3.module_el8.0.0+181+899d6349Architecture:x86_64Size:11MSource:mysql-8.0.17-3.module_el8.0.0+181+899d6349.src.rpmRepository:AppStreamSummary:MySQLclientprogramsandsharedlibrariesURL:http://www.mysql.comLicense:GPLv2withexceptionsandLGPLv2andBSDDescription:MySQLisamulti-user,multi-threadedSQLdatabaseserver.MySQLisa:client/serverimplementationconsistingofaserverdaemon(mysqld):andmanydifferentclientprogramsandlibraries.Thebasepackage:containsthestandardMySQLclientprogramsandgenericMySQLfiles.

8.0.17をインストールします。

MySQL8.0.17をインストール
# dnf install @mysql:8.0
PHP用のライブラリ群もインストール
# dnf install php-mysqlnd

以上でMySQL8.0のインストールが完了ですが、自動起動の設定をして、MySQLを起動させます。

# systemctl enable mysqld
Created symlink /etc/systemd/system/multi-user.target.wants/mysqld.service → /usr/lib/systemd/system/mysqld.service.
# systemctl start mysqld

(3) MySQLの初期設定

MySQLの初期設定は簡単です。私は全てyで設定しました。

# mysql_secure_installation

Securing the MySQL server deployment.

Connecting to MySQL using a blank password.

VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?

Press y|Y for Yes, any other key for No: y

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2
Please set the password for root here.

New password: 

Re-enter new password: 

Estimated strength of the password: 100 
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done! 

(4) WordPress用のMySQLユーザーの作成

後でWordPressの初期設定画面に入れるユーザーを作成しておきます。wpuserというユーザー名にしましたが、任意で。

# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.17 Source distribution

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> CREATE USER 'wpuser'@'localhost' IDENTIFIED BY '任意のパスワード';
Query OK, 0 rows affected (0.02 sec)

mysql> CREATE DATABASE wordpress;
Query OK, 1 row affected (0.01 sec)

mysql> GRANT ALL ON wordpress.* TO `wpuser `@`localhost`;
Query OK, 0 rows affected (0.00 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)

mysql> exit
Bye

5. WordPressのインストール

(1) 前提ソフトのインストール

# dnf install tar curl php-json
Last metadata expiration check: 0:46:02 ago on Mon 01 Jun 2020 02:48:32 AM UTC.
Package tar-2:1.30-4.el8.x86_64 is already installed.
Package curl-7.61.1-11.el8.x86_64 is already installed.
Package php-json-7.4.6-1.el8.remi.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!

(2) WordPressのインストール

WordPressをダウンロードして、NginxのRootディレクトリに展開します。/tmpにダウンロードしましたが、どこでも構いません。tarで展開したら、ディレクトリ丸ごとnginxのRootディレクトリにコピーします。

# cd /tmp
# curl https://wordpress.org/latest.tar.gz --output wordpress.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 11.6M  100 11.6M    0     0  32.9M      0 --:--:-- --:--:-- --:--:-- 32.9M
# tar xf wordpress.tar.gz
# cp -r wordpress /var/www/html

最後にパーミッションとSELinux security contextを変更します。

# chown -R nginx.nginx /var/www/html/wordpress
# chcon -t httpd_sys_rw_content_t /var/www/html/wordpress -R

これでWordPressのインストールは完了です。WordPressのトップページ (http://IPアドレス/wordpress) をブラウザで確認して、以下の画面になっていれば成功です。

スクリーンショット 2020-05-31 午後11.41.02.png

(3) WordPressの構成

WordPressの構成はWordPressの初期画面のLet's go!ボタンをクリックして開始します。

スクリーンショット 2020-05-31 午後11.45.38.png

必要項目を入力したらSubmitすると以下の画面になるのでRun the installationをクリックして実行します。

スクリーンショット 2020-05-31 午後11.46.50.png

WordPressのUsernameやパスワードなどを設定して、Install WordPressをクリックします。

スクリーンショット 2020-05-31 午後11.47.31.png

以下の画面になれば、WordPressのインストールは完了ですので、ログインしてWordPress管理画面に入ります。

スクリーンショット 2020-05-31 午後11.50.26.png

ログイン画面は http://IPアドレス/wordpress/wp-login.php です。

スクリーンショット 2020-05-31 午後11.53.07.png

ここから先はWordPressの世界ですので、好きなスキンでコンテンツを作ってください。

スクリーンショット 2020-05-31 午後11.55.33.png

参考記事

AWS(AmazonLinux2)でPHP7.4インストール

背景

AmazonLinux2リポジトリのPHPのバージョンが古く、以前まではRemi7をリポジトリに登録してAwsにPHP環境を作成していたが、一部でインストールする際など諸々考慮が必要となり、管理が煩雑となることから「amazon-linux-extras」を利用するよう変更したら、諸々楽になったので、メモ

Extraインストール

sudo amazon-linux-extras install php7.4
sudo yum update

phpインストール

sudo yum install php php-mbstring

PHP Docker で composer require linecorp/line-bot-sdk がコケる�

状況

Docker Image : php:7.4.6-fpm-alpine3.11

ログ

# composer require linecorp/line-bot-sdk
Using version ^4.4 for linecorp/line-bot-sdk
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - linecorp/line-bot-sdk 4.4.1 requires ext-sockets * -> the requested PHP extension sockets is missing from your system.
    - linecorp/line-bot-sdk 4.4.0 requires ext-sockets * -> the requested PHP extension sockets is missing from your system.
    - Installation request for linecorp/line-bot-sdk ^4.4 -> satisfiable by linecorp/line-bot-sdk[4.4.0, 4.4.1].

  To enable extensions, verify that they are enabled in your .ini files:
    - 
    - /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini
    - /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini
  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.

the requested PHP extension sockets is missing from your system.

socketsが無いよというお話でした。

対処

Dockerfile内でsocketsをインストールするだけ。

~略~
RUN docker-php-ext-install sockets
~略~

結果

無事、インストール完了

 # composer require linecorp/line-bot-sdk
Using version ^4.4 for linecorp/line-bot-sdk
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing linecorp/line-bot-sdk (4.4.1): Downloading (100%)         
linecorp/line-bot-sdk suggests installing apigen/apigen (Install with roave/better-reflection:dev-master to generate docs)
linecorp/line-bot-sdk suggests installing roave/better-reflection (Required by apigen/apigen:dev-master)
Package jakub-onderka/php-console-color is abandoned, you should avoid using it. Use php-parallel-lint/php-console-color instead.
Package jakub-onderka/php-console-highlighter is abandoned, you should avoid using it. Use php-parallel-lint/php-console-highlighter instead.
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
34 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

LinuxでApache2.4(httpd 2.4.43)+PHP7.4をソースコンパイルしてWebサーバー構築 - 2.PHP導入編

前提と準備

Linuxサーバー構築の記事

前回はApache httpd 2.4.43をソースコンパイルでWebサーバーで構築しましたが、今回は、前回に引き続き、ApacheにWebアプリサーバーの基幹であるPHP 7.4をソースコンパイルで導入します(⑅•ᴗ•⑅)

環境

  • Webサーバープログラム:Apache 2.4.43 + PHP 7.4.6(ソースコンパイル)
  • クライアント:Windows10 Pro
  • サーバーのアーキテクチャ:x64(動作はHyper-Vの第2世代で確認) Linuxのディストリビューション:CentOS 8.1 / openSUSE 15.1 Leap / Ubuntu 20.04(すべて64bit)

前提

  • ユーザーはrootでインストール(私の検証ではadminという管理者アカウントにて、そこからsudoで処理しています)
  • どのディストリビューションでも、ファイアウォールはfirewalldを使う(ディストリビューション独自のファイアウォールコマンドは使用しない)
  • 前回の記事のApache導入を完了していること(動作が確認できて、Webサーバーとして構築されていること)

サーバー条件

IPアドレス

  • クライアント:192.168.1.11
  • Webサーバー:192.168.1.18(どのディストリビューションでも同じIPアドレスで検証)
  • 所属ネットワークセグメント:192.168.1.0/24 Webサーバー.png

パッケージを個別ダウンロードしてインストールする機能とバージョン(2020年6月時点)

  • zlib-1.2.11.tar.gz
  • apr-1.7.0.tar.gz
  • apr-util-1.6.1.tar.gz
  • mysql80-community-release-el8-1.noarch.rpm (CentOS 8.1)
  • mysql80-community-release-sl15-3.noarch.rpm (openSUSE 15.1)
  • mysql-apt-config_0.8.15-1_all.deb (Ubuntu 20.04)
  • oniguruma-devel-6.8.2-1.el8.x86_64.rpm (CentOS 8.1)
  • httpd-2.4.43.tar.gz
  • php-7.4.6.tar.gz

それ以外の必要なパッケージは、ディストリビューションの標準パッケージコマンド(dnfやaptなど)でインストールし、個別ダウンロードは不要です。

ダウンロードについては、公式サイトにアクセスして、そこからダウンロードしてFTPで転送するか、ダウンロードファイルのURLさえわかれば、wgetで入手することもできますが、入手方法は省略しています。

作業手順

PHPのインストール

PHPのソースコンパイルに必要なライブラリを導入

CentOS8.1
# dnf -y install libxml2 libxml2-devel sqlite-devel
# dnf -y install oniguruma-devel-6.8.2-1.el8.x86_64.rpm
openSUSE15.1
# zypper -n install libxml2-tools libxml2-devel sqlite3-devel oniguruma-devel
Ubuntu20.04
# apt-get -y install libxml2 libxml2-dev libsqlite3-dev libonig-dev

PHP7.3までは、SQLiteと鬼車のライブラリを使わなくてもマルチバイト文字列を扱うようインストールができたのですが、PHP7.4ではソースコンパイルで必須となりました。

CentOS 8.1のみ、鬼車ライブラリ(oniguruma-devel-6.8.2-1.el8.x86_64.rpm)はデフォルトパッケージには存在しないため、CentOSパッケージのダウンロードページから持ってきました。

2020年6月現在:
http://mirror.centos.org/centos/8/PowerTools/x86_64/os/Packages/oniguruma-devel-6.8.2-1.el8.x86_64.rpm

PHPのソースコンパイル

コンパイルには20~30分前後かかる見込みです。Apacheより規模が大きいので、コンパイル中はコーヒー1杯で休憩をはさんだほうがいいかもしれません(*˘︶˘*).。.:*♡

# cd [php-7.4.6.tar.gzが置いてあるディレクトリ]
# tar xvzf php-7.4.6.tar.gz
# cd php-7.4.6/
# ./configure --with-apxs2=/usr/local/apache2/bin/apxs --with-mysqli --with-pdo-mysql --enable-mbregex --enable-mbstring
# make
# make test
  • MySQLはPDOを有効にします
  • マルチバイト文字列を有効にします

「make」「make test」が時間がかかるコマンドです。
途中でエラーがなければコンパイル完了です。
なお、CentOS 8.1とUbuntu 20.04でmake testで私がテストしたところ、なんとバグは一つもなかったのです( ๑・∞・๑ ) …OSと相性がいいのかもしれません
php_test_fail.png

PHPのインストール

コンパイルが完了したら、インストールします。

CentOS8.1・Ubuntu20.04
# make install
Installing PHP SAPI module:       apache2handler
/usr/local/apache2/build/instdso.sh SH_LIBTOOL='/opt/apr-1.7.0/build-1/libtool' libphp7.la /usr/local/apache2/modules
/opt/apr-1.7.0/build-1/libtool --mode=install install libphp7.la /usr/local/apache2/modules/
libtool: install: install .libs/libphp7.so /usr/local/apache2/modules/libphp7.so
libtool: install: install .libs/libphp7.lai /usr/local/apache2/modules/libphp7.la
libtool: warning: remember to run 'libtool --finish /home/admin/php-7.4.6/libs'
chmod 755 /usr/local/apache2/modules/libphp7.so
[activating module `php7' in /usr/local/apache2/conf/httpd.conf]
Installing shared extensions:     /usr/local/lib/php/extensions/no-debug-zts-20190902/
Installing PHP CLI binary:        /usr/local/bin/
Installing PHP CLI man page:      /usr/local/php/man/man1/
Installing phpdbg binary:         /usr/local/bin/
Installing phpdbg man page:       /usr/local/php/man/man1/
Installing PHP CGI binary:        /usr/local/bin/
Installing PHP CGI man page:      /usr/local/php/man/man1/
Installing build environment:     /usr/local/lib/php/build/
Installing header files:          /usr/local/include/php/
Installing helper programs:       /usr/local/bin/
  program: phpize
  program: php-config
Installing man pages:             /usr/local/php/man/man1/
  page: phpize.1
  page: php-config.1
/home/admin/php-7.4.6/build/shtool install -c ext/phar/phar.phar /usr/local/bin
ln -s -f phar.phar /usr/local/bin/phar
Installing PDO headers:           /usr/local/include/php/ext/pdo/
openSUSE15.1
# make install
Installing PHP SAPI module:       apache2handler
/usr/local/apache2/build/instdso.sh SH_LIBTOOL='/opt/apr-1.7.0/build-1/libtool' libphp7.la /usr/local/apache2/modules
/opt/apr-1.7.0/build-1/libtool --mode=install install libphp7.la /usr/local/apache2/modules/
libtool: install: install .libs/libphp7.so /usr/local/apache2/modules/libphp7.so
libtool: install: install .libs/libphp7.lai /usr/local/apache2/modules/libphp7.la
libtool: warning: remember to run 'libtool --finish /home/admin/php-7.4.6/libs'
chmod 755 /usr/local/apache2/modules/libphp7.so
[activating module `php7' in /usr/local/apache2/conf/httpd.conf]
Installing shared extensions:     /usr/local/lib64/extensions/no-debug-zts-20190902/
Installing PHP CLI binary:        /usr/local/bin/
Installing PHP CLI man page:      /usr/local/php/man/man1/
Installing phpdbg binary:         /usr/local/bin/
Installing phpdbg man page:       /usr/local/php/man/man1/
Installing PHP CGI binary:        /usr/local/bin/
Installing PHP CGI man page:      /usr/local/php/man/man1/
Installing build environment:     /usr/local/lib64/build/
Installing header files:          /usr/local/include/php/
Installing helper programs:       /usr/local/bin/
  program: phpize
  program: php-config
Installing man pages:             /usr/local/php/man/man1/
  page: phpize.1
  page: php-config.1
/home/admin/php-7.4.6/build/shtool install -c ext/phar/phar.phar /usr/local/bin
ln -s -f phar.phar /usr/local/bin/phar
Installing PDO headers:           /usr/local/include/php/ext/pdo/

PHPのライブラリのいくつかは「/usr/local/include/php/」「/usr/local/lib/php/」のように、用途に応じて複数の配置場所へ配置されます。openSUSE 15.1の場合は「/usr/local/lib64/php/」に配置されるものもあります。
また、この時点で、PHPのDLL(ダイナミックリンクライブラリ、外部拡張アプリケーションに該当する)は自動的にApacheのフォルダに配備されます

次に、PHPの環境設定ファイルのコピーを行います。
まずはphp.iniをPHPのライブラリフォルダ/usr/local/lib(openSUSEの場合はlib64)/にコピーします。

CentOS8.1・Ubuntu20.04
# cp php.ini-development /usr/local/lib/php.ini
# ls -l /usr/local/lib
合計 328
-rw-r--r-- 1 root root 144402  6月 24 12:34 libz.a
lrwxrwxrwx 1 root root     14  6月 24 12:34 libz.so -> libz.so.1.2.11
lrwxrwxrwx 1 root root     14  6月 24 12:34 libz.so.1 -> libz.so.1.2.11
-rwxr-xr-x 1 root root 113656  6月 24 12:34 libz.so.1.2.11
drwxr-xr-x 4 root root     37  6月 24 16:19 php
-rw-r--r-- 1 root root  72278  6月 24 16:21 php.ini
drwxr-xr-x 2 root root     21  6月 24 12:34 pkgconfig
openSUSE15.1
# cp php.ini-development /usr/local/lib64/php.ini
# ls -l /usr/local/lib64
合計 72
drwxr-xr-x 1 root root   338  6月 25 16:59 build
drwxr-xr-x 1 root root    42  6月 25 16:59 extensions
-rw-r--r-- 1 root root 72278  6月 25 17:01 php.ini

Apache側の設定

Apacheで、PHPファイルが認識できるようにする必要があるので編集します。PHPのDLLは、PHPインストール時にhttpd.confで自動的に書き込まれるので追加不要です。httpd.conf側でやるべきことは、PHPのMIMEタイプがApacheで認識されればよいのです。

# vi /usr/local/apache2/conf/httpd.conf
/usr/local/apache2/conf/httpd.conf
<IfModuledir_module>
    DirectoryIndex index.html ← 「index.php」を追加
</IfModule><IfModulemime_module>
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
    …
    AddType application/x-httpd-php .php  ←   この行を追加
</IfModule>

PHPの環境設定

PHPの文字コードや参照ライブラリの設定を変えます。

PHPをコンパイルインストールした際に、ディストリビューションによって、php.iniの格納ディレクトリが違います。openSUSEのみ、/usr/local/lib64/にPHPが含まれていますので、ライブラリフォルダ「lib」の違いが紛らわしいです

CentOS8.1・Ubuntu20.04
# vi /usr/local/lib/php.ini
openSUSE15.1
# vi /usr/local/lib64/php.ini
php.ini
# 内容が長いので、一部修正するもののみ、修正後のもののみを記載します。セミコロン「;」は外します。「;」外すだけの行も含めて記載されています
output_buffering=Ondefault_charset="UTF-8"[CentOS 8.1・Ubuntu 20.04]include_path=".:/usr/local/include/php:/usr/local/lib/php"[openSUSE 15.1]include_path=".:/usr/local/include/php:/usr/local/lib64"extension_dir="/usr/local/include/php/ext"date.timezone=Asia/Tokyombstring.language=Japanesembstring.encoding_translation=Offmbstring.detect_order=UTF-8, SJIS, EUC-JP, JIS, ASCIImbstring.substitute_character=none

PHPの動作確認

PHP確認用のページ

まずは確認用のPHPページを作ります。

# vi /usr/local/apache2/htdocs/phpi.php
phpi.php
<?phpphpinfo();?>

なぜPHPファイルを/usr/local/apache2/htdocs/に配置する??って聞かれますが、Apacheをソースコンパイルでインストールすると、Webページデータの格納フォルダは「/usr/local/apache2/htdocs/」がデフォルトだからです。まぁ、httpd.confで格納先を変更できますが、今回はそれは割愛します。

Apacheの再起動

# systemctl stop httpd
# systemctl start httpd
# systemctl status httpd

再起動しないとPHPのモジュールをロードした状態でApacheが動作しないので、一度止めてから、再スタートします。その後、statusで起動していることを確認します。
phpinfo.png
PHPが正しく認識できました!!(*´꒳`*)

pdo.png
PDOもちゃんと認識できています。そうしないとMySQLとSQLiteが使えないからね…( ´ •̥ ̫ •̥ ` )

susephpi.png

openSUSE 15.1の場合だと、php.iniの場所が「/usr/local/lib64」となって認識されていることがわかります。

次回

MySQLを導入して、PHPのWebアプリサーバーがデータベースを利用できるようにします

LinuxでApache2.4(httpd 2.4.43)+PHP7.4をソースコンパイルしてWebサーバー構築 - 3.MySQL8.0導入編

前提と準備

Linuxサーバー構築の記事

前回はApache2.4にPHP7.4をソースコンパイルでWebアプリ環境を構築しましたが、今回は、前回に引き続き、データベースサーバーを構築します(⑅•ᴗ•⑅)

データベースサーバーはWebサーバー本体と切り離し可能ですが、今回は簡単なため、Webサーバーと一体でデータベースのMySQLを構築します

環境

  • Webサーバープログラム:Apache 2.4.43 + PHP 7.4.6 + MySQL 8.0
  • クライアント:Windows10 Pro
  • サーバーのアーキテクチャ:x64(動作はHyper-Vの第2世代で確認) Linuxのディストリビューション:CentOS 8.1 / openSUSE 15.1 Leap / Ubuntu 20.04(すべて64bit)

前提

  • ユーザーはrootでインストール(私の検証ではadminという管理者アカウントにて、そこからsudoで処理しています)
  • どのディストリビューションでも、ファイアウォールはfirewalldを使う(ディストリビューション独自のファイアウォールコマンドは使用しない)
  • 前回の記事のApache+PHP導入を完了していること

サーバー条件

IPアドレス

  • クライアント:192.168.1.11
  • Webサーバー:192.168.1.18(どのディストリビューションでも同じIPアドレスで検証)
  • データベースサーバー:(Webサーバーと一体)
  • 所属ネットワークセグメント:192.168.1.0/24 Webサーバー.png

パッケージを個別ダウンロードしてインストールする機能とバージョン(2020年6月時点)

  • zlib-1.2.11.tar.gz
  • apr-1.7.0.tar.gz
  • apr-util-1.6.1.tar.gz
  • mysql80-community-release-el8-1.noarch.rpm (CentOS 8.1)
  • mysql80-community-release-sl15-3.noarch.rpm (openSUSE 15.1)
  • mysql-apt-config_0.8.15-1_all.deb (Ubuntu 20.04)
  • oniguruma-devel-6.8.2-1.el8.x86_64.rpm (CentOS 8.1)
  • httpd-2.4.43.tar.gz
  • php-7.4.6.tar.gz

それ以外の必要なパッケージは、ディストリビューションの標準パッケージコマンド(dnfやaptなど)でインストールし、個別ダウンロードは不要です。

ダウンロードについては、公式サイトにアクセスして、そこからダウンロードしてFTPで転送するか、ダウンロードファイルのURLさえわかれば、wgetで入手することもできますが、入手方法は省略しています。

作業手順

MySQL 8.0の導入

MySQLのインストール

ディストリビューションの標準パッケージコマンドにあるMySQLは、バージョンが古いものや互換であるMariaDBであることが多いため、明示的なMySQLのバージョンを導入する場合は、MySQL公式からレポジトリをダウンロードする必要があります。

ダウンロードしたレポジトリを適用し、MySQLをインストールします。

CentOS8.1
# cd [mysql80-community-release-el8-1.noarch.rpmの配置されているディレクトリ]
# dnf -y install mysql80-community-release-el8-1.noarch.rpm
# dnf -y install mysql-server
openSUSE15.1
# cd [mysql80-community-release-sl15-3.noarch.rpmの配置されているディレクトリ]
# zypper install mysql80-community-release-sl15-3.noarch.rpm
…(中略)…
mysql80-community-release-sl15-3.noarch (RPM ファイルキャッシュ): 署名の検証に失敗しました [4-署名の公開鍵がありません]
中止(A)、再試行(R)、無視(I)? [a/r/i] (a):  ← 「i」で続行する

# zypper install mysql-community-server
鍵を拒否しますか(R)? 今後ずっと信頼しますか(A)? [r/a/?] (r):  ← 「a」で続行する
(あとはインストールするか問われるので「y」を選択)
Ubuntu20.04
# cd [mysql-apt-config_0.8.15-1_all.deb.debの配置されているディレクトリ]
# dpkg -i mysql-apt-config_0.8.15-1_all.deb

Which MySQL product do you wish to configure?
→「MySQL Server & Cluster」を選択

Which server version do you wish to receive?
→「mysql-8.0」を選択

Which MySQL product do you wish to configure? に戻る
→「Ok」を選択

# apt-get -y update  ← アップデート後は再起動することが望ましい

# apt-get -y install mysql-client mysql-server
→ 途中MySQLのroot(Linuxのrootではない)のパスワードを設定する必要がある

なお、MySQL 8.0の場合認証基盤がハッシュ値対応で、しかもデフォルトでハッシュ値ベースの認証基盤を有効化している場合があります。しかし、これはPHP7でも対応していないらしく(7.4になった今でも対応している話は聞いていない)、PHPで平文パスワードで記載してアクセスすると認証がうまくできない。そのため、/etc/my.cnfを修正する必要がある(CentOS8.1の場合は/etc/my.cnfでインクルードしている/etc/my.cnf.d/mysql-default-authentication-plugin.cnfを編集する)。

またUbuntu 20.04では、MySQLのapt-getでのインストール中に、MySQLのroot(Linuxのrootではない)パスワードの設定と「Select default authentication plugin」というように認証基盤の選択ができるのでありがたい(✿´ ꒳ ` )

openSUSE15.1
# vi /etc/my.cnf
CentOS8.1
# vi /etc/my.cnf.d/mysql-default-authentication-plugin.cnf
my.cnfまたはmysql-default-authentication-plugin.cnf
default-authentication-plugin=mysql_native_password ← 先頭の「#」ははずす
Ubuntu20.04の場合
[認証基盤を/etc/my.cnfの修正で行う必要はない。インストール時に選択する]
インストール中に「Select default authentication plugin」と問われる
→「Use Legacy Authentication Method」を選択する

MySQLの起動

CentOS8.1
# systemctl start mysqld
# systemctl enable mysqld
# systemctl status mysqld
openSUSE15.1・Ubuntu20.04
# systemctl start mysql
# systemctl enable mysql
# systemctl status mysql

次に、MySQLの起動を行った後に、MySQLの初期パスワードを確認するため、MySQLのログを確認。なおUbuntu 20.04ではパスワード設定をあらかじめインストール時に実施したので、初期パスワードをログから確認する作業は不要。

CentOS8.1
# less -r /var/log/mysqld.log
openSUSE15.1
# less -r /var/log/mysql/mysqld.log

その際、「[Note] A temporary password is generated for root@localhost:~」が存在すれば、初期パスワードが設定されている。なければ設定されていない(CentOS 8.1のMySQL 8.0は設定されていない)。このパスワードを用いて、MySQLコマンドラインを実行する

Ubuntu20.04はこの手順は飛ばす
[MySQL初期パスワードなし]
# mysql -u root
[MySQL初期パスワードあり]
# mysql -u root -p  ← 初期パスワードを入力してログイン

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY '<MySQLのrootパスワード(Linuxのrootパスワードではない)>';
Query OK, 0 rows affected (0.00 sec)

mysql> exit

ここで、MySQLの実際に運用想定のパスワードを設定する。そこで、rootというのはLinuxのrootではなく、MySQLの中のrootであることに注意したい。

「Query OK, 0 rows affected (0.00 sec)」と出力されれば、MySQLのrootパスワードの設定が完了するので、exitで終了する。

では、設定後のMySQLのrootパスワードでログインを試す

# mysql -u root -p
Enter password:  ← MySQLのrootパスワードを入力

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 8.0.17 Source distribution

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

こうなればMySQLのrootログインが成功となる。または、以下でも確認できた

# mysqladmin ping -u root -p
Enter Password:  ← MySQLのrootパスワードを入力
mysqld is alive

「mysqld is alive」となればOK

MySQLを試す

ユーザーとデータベースを作ってみる

MySQLが動いたということなので、実際ユーザーとデータベースを作ってみます。

  • データベース名:manutest
  • テストユーザー名:test
  • テストユーザーパスワード:test0
  • 文字コード:UTF-8

最初にMySQLのrootでログインします。

# mysql -u root -p
Enter password:  ← MySQLのrootパスワードを入力

mysql> CREATE DATABASE manutest CHARACTER SET utf8;
↑ データベース「manutest」を文字コードUTF-8で作成する

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| manutest           |  ← 作成したデータベースが表示される
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> CREATE USER 'test'@'localhost' IDENTIFIED BY 'test0';
↑ ユーザー名「test」をパスワード「test0」にて作成する

mysql> GRANT ALL ON manutest.* TO 'test'@'localhost';
↑ データベース「manutest」に対して、ユーザー「test」が全機能を使えるようにする

エラーがなく「Query OK, 0 rows affected」が表示されれば、データベースとユーザーは作成完了。

パスワードポリシーエラーが発生する場合

もしパスワードポリシーに反しているとエラーが発生してしまう場合は、ポリシーの状態を確認して、変更できるみたいです。ちなみにCentOS 8.1では存在せず、openSUSE 15.1では存在していました。

mysql> SHOW VARIABLES LIKE 'validate_password%';
↑ Empty setならポリシーは存在しないが、もし存在する場合は、初期では
 validate_password_lengthが8、validate_password.policyが「MEDIUM」

mysql> SET GLOBAL validate_password.length=4;
mysql> SET GLOBAL validate_password.policy=LOW;
↑ これで4文字以内のパスワード、試験目的で簡単なパスワードを設定できる

mysql> SET GLOBAL validate_password.length=8;
mysql> SET GLOBAL validate_password.policy=MEDIUM;
↑ 戻す場合

では、作成したテスト用のユーザーとデータベースで、MySQLを使えるか試します。

mysql> exit
Bye

# mysql -u test -p
Enter password:  ← パスワード「test0」を入れる
(中略)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| manutest           |  ← 作成したデータベースが表示されれば成功
+--------------------+
2 rows in set (0.00 sec)

テーブルを作成してデータを入れてみる

では、作成したデータベースに、テーブルを作ってそこにデータを入れてみます!

今回は、このテーブルをテスト用に作成します。
テーブル名:testtb

idnamememo
INTVARCHAR(64)VARCHAR(256)
必須属性PRIMARY KEYNOT NULL初期値NULL
その他属性AUTO_INCREMENT--
mysql> USE manutest;
mysql> CREATE TABLE testtb (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, memo VARCHAR(256) DEFAULT NULL);
Query OK, 0 rows affected (0.11 sec)

mysql> SHOW TABLES;
+--------------------+
| Tables_in_manutest |
+--------------------+
| testtb             |
+--------------------+
1 row in set (0.00 sec)

テーブルの作成に成功したら、実際にデータを入れてみます。
テストテーブルtesttbは、idが自動採番で、nameが必須入力となるため、例として以下の値を挿入します。

  • id:(自動採番のため省略)
  • name:テスト
  • memo:Only for test.
mysql> INSERT INTO testtb (name, memo) VALUES ('テスト', 'Only for test.');
mysql> SELECT * FROM testtb;
+----+-----------+----------------+
| id | name      | memo           |
+----+-----------+----------------+
|  1 | テスト    | Only for test. |
+----+-----------+----------------+
1 row in set (0.00 sec)

このように登録したデータが文字化けすることなくデータベースに登録されました♪
成功です!

PHP上でMySQLを扱ってみる

MySQLのインストールも、動作も問題なく確認できたので、いよいよPHPからMySQLを扱ってみたいと思います。

MySQLは終了して、PHPページを作成します。

PHPからMySQLの接続を確認

まずはPHPでPOSTにて受け取ったデータをMySQLデータベースへ登録する受信PHPを作成します!っとその前に、PHPでMySQLに接続できるかちゃんと確かめないといけません(*˘ᗜ˘*;)

接続確認ページを作ります

# cd ~
# vi connect.php
connect.php
<?php$dsn='mysql:dbname=manutest; host=127.0.0.1';$usr='test';$pass='test0';try{$db=newPDO($dsn,$usr,$pass);print'Connection successful.';$db=NULL;}catch(PDOException$e){die('Connect error : '.$e->getMessage());}?>

これを、Apache導入時にインストールした先のWebページの格納場所 /usr/local/apache2/htdocs/に移動しましょう!

# mv connect.php /usr/local/apache2/htdocs/

PHPページの配置なので、特にApacheを再起動することは不要なので、https://[LinuxサーバーのIPアドレス]/connect.php と入力して確認。今回はLinuxサーバーのIPアドレスは192.168.1.18なので、ブラウザでhttps:~の後に、192.168.1.18/connect.phpをURLを入力してアクセス。

conn.png
connect.phpでは接続成功すると上記のメッセージを出力するので、↑の画像は接続が成功したんです!!

PHPからMySQLにデータを登録できるかを確認

続いて、いよいよデータが登録できるかを確認します。
再びWebページを作ります。

まずは登録フォームから。

# vi test_form.html
test_form.html
<!DOCTYPE html><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>テストページ - Test page</title></head><body><formmethod="POST"action="test_form.php"><inputtype="text"name="name"/><inputtype="text"name="memo"/><inputtype="submit"value="Submit"/></form></body></html>

続いて、フォームから受け取ったデータをMySQLデータベースへ追加するPHPを作成

# vi test_form.php
test_form.php
<!DOCTYPE html><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>テスト入力 - Test insert</title></head><body><?phpfunctiongetDb(){$dsn='mysql:dbname=manutest; host=127.0.0.1';$usr='test';$pass='test0';try{$db=newPDO($dsn,$usr,$pass);$db->exec('SET NAMES utf8');}catch(PDOException$e){die('Connect error : '.$e->getMessage());}return$db;}try{$db=getDb();$stt=$db->prepare('INSERT INTO testtb (name, memo) VALUES (:one, :two)');$stt->bindValue(':one',$_POST['name']);$stt->bindValue(':two',$_POST['memo']);$stt->execute();print$_POST['name'].' - '.$_POST['memo'].' : Insert OK.';$stt=$db->query("SELECT * FROM testtb");?><tableborder="1"><?phpwhile($row=$stt->fetch(PDO::FETCH_ASSOC)){?><tr><td><?phpprint$row['name'];?></td><td><?phpprint$row['memo'];?></td></tr><?php}?></table><?php$db=NULL;}catch(PDOException$e){die('Process error : '.$e->getMesssage());}?></body></html>

上記2ファイルをApacheのWebページデータの場所へ。

# mv test_form.* /usr/local/apache2/htdocs/

では、ブラウザでhttps://[IPアドレス]/test_form.htmlにアクセスしてみます。

reg1.png
適当に左右のフォームに文字列を入れて…(なるべく日本語で)、Submitを押します

reg2.png
成功しました!入力されたデータがPHPにPOSTされて、MySQLのデータベースに登録されました。ではコマンドラインからも確認してみましょう。

PHPでは、manutestというデータベースに、ユーザーtestで接続しているので、コマンドからもtestでMySQLにログインします。

# mysql -u test -p
Enter password:  ← パスワード「test0」を入力

mysql> USE manutest;
mysql> SELECT * FROM testtb;
+----+-----------------+----------------+
| id | name            | memo           |
+----+-----------------+----------------+
|  1 | テスト          | Only for test. |
|  2 | パンケーキ      | 食べたい       |
+----+-----------------+----------------+
2 rows in set (0.00 sec)

ちゃんと追加されていました!!(*˘ᗜ˘*).。.:*♡

あとがき

ApacheとPHP+MySQLは、私の過去の実務経験からも、大企業までも頻繁にこのパターンで使われていました。逆にJavaは少なかったかな…と感じています。

PHPのほうがJavaの商用ライセンスよりも安価でオープンなので、大企業でも、もちろん中小個人でも導入しやすいのかな…って感じがします。

LinuxへのApache+PHP+MySQLに導入する費用としては、ライセンスがいらないので、あとは給料に対する労力の費用と、中古PCやラズパイ関係なく、装置の費用なのかな…と感じがします。

導入費用が1時間2500円であれば、Webサーバーとファイルサーバーを新規構築するのに2時間かければ、合計5000円。それに、中古PC or ラズパイとHDDやSSDの新品の組み合わせが3~40000円となれば、新規導入費用は5万円弱なので、高価なWindows Serverマシンを30万円かけて買うよりは、6分の1以下と、中小企業や個人経営程度でもITソリューションの潤いはやってくるのかな…と感じる。

特に、ラズパイなどIoTの場合は、センサーなどの物性分野とハイブリッドで組み合わせて、データ化できるのも期待されるので、今後はいろいろと視野を広げて、Qiitaに載せてみたいと思う( ˙꒳​˙ᐢ )

今後と次回

PHPによるWebアプリサーバー構築のほかにも、Java(OpenJDK)によるWebアプリサーバーについても、構築に触れてみたい

AWS+CentOS+LAMP+WordPress構築

はじめに

私自身、AWSとかLAMP環境とか全く分からない状況から始めた。
EC2にはAmazon Linux2があるが、中身を理解するなら、個人的な意見になるがCentOSで始めたほうが良いと思う。

このページでは、WordPressが使える環境を構築する。OSはCentOS7を使用(Amazonマーケットプレイスにあるやつ)。SSH接続はMacのターミナルから実施。

無料で実施できるので金が無い人でも安心。
(クレカ登録必要なのと、AWSの仕様上使いすぎると金取られるので、サーバーを24時間5台ぐらいつけっぱなしだけは辞めておいたほうが良い。)

セットアップは全体的に以下のサイトを参考。

ネコでもわかる!さくらのVPS講座

AWSのアカウントを作成する

以下のページからAWSのアカウントを作成しコンソールにログインする。

https://portal.aws.amazon.com/billing/signup#/start

仮想マシンを起動する

  1. AMI(Amazonマシンイメージ)を選択

  2. インスタンスタイプを選択

  3. キーペアを作成

    名前は自由。作成するとpemファイルがダウンロードされるので保管する。

鍵の権限設定

これを実行しないと、SSH接続のタイミングでエラーが発生する。pemファイルはSSHKeyフォルダを作成し配置。

chmodコマンド

chmod 600 /SSHkey/CentOS7Key.pem

SSH接続

ブラウザからも接続できるが、ターミナルから実行した方が楽。

MacのターミナルでEC2にSSHでログインする

  ssh -i /SSHkey/CentOS7Key.pem centos@ec2-18-218-155-69.us-east-2.compute.amazonaws.com

ユーザーはrootではなくcentosでログインする必要がある。

CentOS環境整備

  • OSバージョン確認
cat /etc/redhat-release

CentOS Linux release 7.7.1908 (Core)

  • OSアップデート
sudo yum update
  • パスワード設定
sudo passwd centos

これでパスワードを設定していなくても、パスワードを設定できる。

Apache httpdセットアップ

Apache2もあるが、今回はhttpdを使用する。

  • httpdインストール
sudo yum install httpd
  • httpd起動

起動時、centosユーザのパスワードが求められる。

  systemctl start httpd
  • ファイアウォールの設定

http通信を許可するためにポート解放する必要がある。
また、設定前に「firewalld」をインストールする必要がある。
設定後は「firewalld」を再起動する。

sudo yum install firewalld
  sudo firewall-cmd --add-service=http --zone=public --permanent
  systemctl restart firewalld
  • AmazonEC2のセキュリティグループの設定

本来ならこれだけで接続できるが、AmazonEC2の場合はセキュリティグループの設定もしなければならない。以下のリンクの「セキュリティーグループの設定」を参照。

AWS EC2でWebサーバーを構築してみる

  • ブラウザから接続

AmazonEC2のインスタンスの説明にある、パブリックDNSかIPv4パブリックIPをコピペしてhttp接続をする。

以下、パブリックDNSの例。

http://ec2-18-218-155-69.us-east-2.compute.amazonaws.com/

  • サーバー起動時設定

サーバー起動タイミングでhttpdが起動する設定。

  systemctl enable httpd

設定されたかの確認は以下のコマンド。以下のコマンドを実行するとviエディタで開かれる。閉じるときは「:q」。

  systemctl list-unit-files -t service

PHPセットアップ

インストールするときのバージョンに注意する。
以下のやり方を参考。

CentOS7でphp7.4をインストールしようとして躓いた話

  • Remiリポジトリの情報

以下が公式リンク。

http://rpms.remirepo.net/

CentOS7を使用する場合は「remi-release-7.rpm」でいいと思われる。

  • epel-release インストール
sudo yum install epel-release
  • Remiリポジトリインストール
sudo rpm -ivh http://ftp.riken.jp/Linux/remi/enterprise/remi-release-7.rpm
  • php確認
  yum list php*
  • httpd+php7.4+mariadb インストール
sudo yum install httpd mariadb-server php74
  • phpファイル作成

/var/www/html/の下にindex.phpを作成。

<html>
    <body>
      <?php echo "Hello World! php" ?>
    </body>
  </html>

ファイル作成コマンドは以下の通り。

sudo touch index.php

ファイル編集コマンドは以下の通り。

sudo vi index.php

ファイルアップ後再起動は以下の通り。

  systemctl restart httpd

現在の状態だとphpが見えないので、以下を実施する。

  • /etc/yum.repos.d/remi-safe.repo修正(無効にする)
  enabled=1

  enable=0
  • /etc/yum.repos.d/remi-php74.repo修正(有効にする)
  enabled=0

  enabled=1
  • /etc/yum.repos.d/CentOS-Base.repo修正(php除外)

baseとupdatesに以下を追加。(おそらく16行目付近と24行目付近)

  exclude=php*
  • 以前のphpをアンインストール

suでrootユーザに変更してから実行する。

for i in`rpm -qa |grep php`;do yum -y remove $i;done
  • phpを再度インストール
  yum install php
  • サーバー再起動
  systemctl restart httpd.service
  • ページ確認

以下のURLのように/index.phpを後ろにつける。

http://ec2-18-218-155-69.us-east-2.compute.amazonaws.com/index.php

MariaDBセットアップ

既に上のPHP手順でインストールはしている。
一応確認。

  • MariaDBのインストール
  yum install mariadb-server
  • MariaDBの有効化とスタート
  systemctl enable mariadb
  systemctl start mariadb
  • 初期設定
  mysql_secure_installation

具体的な手順は以下参照。

ネコでもわかる!さくらのVPS講座 ~第四回「phpとMariaDBをインストールしよう」

  • ログイン
  mysql -u root -p

PHPMyAdminのセットアップ

DBを操作しやすくするツールである。

  • phpMyAdminのインストール
  yum-config-manager --enable remi
  yum install phpMyAdmin
  • /etc/httpd/conf.d/phpMyAdmin.conf修正




のRequire local
をRequire all grantedに修正

  • phpMyAdminの確認

以下のURLのように後ろに/phpmyadmin/をつける

http://ec2-18-218-155-69.us-east-2.compute.amazonaws.com/phpmyadmin/

WordPressのセットアップ

  • wordpress用のDB作成

以下のリンクを実施。

ネコでもわかる!さくらのVPS講座 ~第八回「WordPressサイトを公開しよう」

  • wordPress取得
  yum install wget
  cd /var/www/html
  wget https://ja.wordpress.org/wordpress-5.4.2-ja.tar.gz
  rm index.php5
  tar xvzf wordpress-5.4.2-ja.tar.gz
  • wordPressセットアップ
rm wordpress-5.4.2-ja.tar.gz
  mv wordpress/*.rmdir wordpress
  chown-R apache:apache *
  systemctl restart httpd
  • 動作確認

いつも通りURLにアクセス。

おわりに

これで画面にWordPressの設定画面が見えます。よって構築は終わりです。お疲れ様でした。
結構細かく書いて、GUI操作だけリンクに書くようにしてるので、この手順を完全に真似すれば構築できると思います。もし分からない、もっといいやり方ある等意見ありましたらコメントお願いします。

読解メモ: PHP RFC: Make constructors and destructors return void

この記事は @carrotRakkoPHP RFC: Make constructors and destructors return voidを読み解いて自分なりにまとめなおしたものです。

英語の解釈や PHP の仕様/実装などなどについて間違っている部分を見つけたらご指摘くださると幸いです。

この記事を書いている時点で読んでいるリビジョンは 2020/07/02 23:13 のものです。

__construct()の返り値の型指定

型指定の仕方を3パターン考えてみます↓

  1. 型指定なし: __construct()
  2. 型指定あり & void: __construct(): void
  3. 型指定あり & void以外: __construct(): bool

PHP 7.4.x では(事実)

型指定の仕方どうなる
型指定なし: __construct()問題なし
型指定あり & void: __construct(): voidFatal error
型指定あり & void以外: __construct(): boolFatal error

PHP 8.0 では(提案)

型指定の仕方どうなる
型指定なし: __construct()問題なし
型指定あり & void: __construct(): void問題なし
型指定あり & void以外: __construct(): boolFatal error

PHP 8.1/9.0 では(提案)

PHP 8.0 では(提案)と同じです。

型指定の仕方どうなる
型指定なし: __construct()問題なし
型指定あり & void: __construct(): void問題なし
型指定あり & void以外: __construct(): boolFatal error

__construct()に返り値の型指定をしなかった場合

暗黙的に返り値の型指定とみなされるパターンを2つ考えてみます↓

  1. なにも指定していないとみなされる(そのまま): 文法違反ですが強いて言うなら __construct(): void|mixed
  2. voidを指定したとみなされる: __construct(): voidとみなされる

PHP 7.4.x では(事実)

なにも指定していないとみなされる(そのまま): 文法違反ですが強いて言うなら __construct(): void|mixed

PHP 8.0 では(提案)

なにも指定していないとみなされる(そのまま): 文法違反ですが強いて言うなら __construct(): void|mixed

PHP 8.1/9.0 では(提案)

voidを指定したとみなされる: __construct(): voidとみなされる

__construct()から値を返した場合

PHP の怒り方を3パターン考えてみます↓

  1. 怒られない
  2. Deprecated
  3. Fatal error

PHP 7.4.x では(事実)

怒られない

PHP 8.0 では(提案)

Deprecated

PHP 8.1/9.0 では(提案)

Fatal error

LinuxでApache2.4(httpd 2.4.43)+PHP7.4でWebサーバー構築 - 4.セキュリティ(chownとfirewalld)編

前提と準備

Linuxサーバー構築の記事

前回まではApache2.4にPHP7.4+MySQL8.0でWebアプリ環境を構築しましたが、セキュリティについても触れないといけませんね;;

Linux標準のfirewalldであれば、iptablesのような難しいコマンドルールだったり、SELinuxといったファイルごとの複雑な制御を行うことなく、誰がどのポートを使うかのみを指定するだけで、ファイアウォールで基本的なセキュリティを設定することができます(もちろん制御が複雑なセキュリティを使ったほうが内部侵入されたときも比較的安心ですが…)

他にもファイルの暗号化も併用したいところだが、話が難しくなるので、今回はfirewalldによるファイアウォール、chownでアクセス権を設定するにとどめておきます

また今回の検証では、別のネットワークセグメントからもアクセスの可否を調べたいので、Webサーバーはまたネットワークアダプタを増設して、もう一つのネットワークセグメントを増設します(192.168.1.0/24を使っていますが、Webサーバーにもう一つネットワークアダプタ(仮想ブリッジですが)増設して、192.168.5.0/24を構築しています)

環境

  • Webサーバープログラム:Apache 2.4.43 + PHP 7.4.6 + MySQL 8.0
  • クライアント(メイン):Windows10 Pro
  • クライアント(増築側):GUIのOSであれば、フリー(ここではInsider Preview使用中のWindows10イタリア語をHyper-V(第1世代)で使用しました)
  • サーバーのアーキテクチャ:x64(動作はHyper-Vの第2世代で確認)
  • Linuxディストリビューション:CentOS 8.1 / openSUSE 15.1 Leap / Ubuntu 20.04(すべて64bit)

前提

  • ユーザーはrootでインストール(私の検証ではadminという管理者アカウントにて、そこからsudoで処理しています)
  • どのディストリビューションでも、ファイアウォールはfirewalldを使う(ディストリビューション独自のファイアウォールコマンドは使用しない)
  • 前回の記事のApache+PHP+MySQLの一式のWebアプリサーバー構築そのものを完了していること

サーバー条件

IPアドレス

  • クライアント(メイン):192.168.1.11
  • クライアント(増築側):192.168.5.2
  • Webサーバー(2ポート):(メイン側IP)192.168.1.18、(増築側IP)192.168.5.1 (どのディストリビューションでも同じIPアドレスで検証)
  • データベースサーバー:(Webサーバーと一体)
  • 所属ネットワークセグメント:(メイン)192.168.1.0/24、(増築)192.168.5.0/24 web-from-2-port.png

パッケージを個別ダウンロードしてインストールする機能とバージョン(2020年6月時点)

  • zlib-1.2.11.tar.gz
  • apr-1.7.0.tar.gz
  • apr-util-1.6.1.tar.gz
  • mysql80-community-release-el8-1.noarch.rpm (CentOS 8.1)
  • mysql80-community-release-sl15-3.noarch.rpm (openSUSE 15.1)
  • mysql-apt-config_0.8.15-1_all.deb (Ubuntu 20.04)
  • oniguruma-devel-6.8.2-1.el8.x86_64.rpm (CentOS 8.1)
  • httpd-2.4.43.tar.gz
  • php-7.4.6.tar.gz

それ以外の必要なパッケージは、ディストリビューションの標準パッケージコマンド(dnfやaptなど)でインストールし、個別ダウンロードは不要です。

ダウンロードについては、公式サイトにアクセスして、そこからダウンロードしてFTPで転送するか、ダウンロードファイルのURLさえわかれば、wgetで入手することもできますが、入手方法は省略しています。

firewalldによるネットワークごとのアクセス制御

現時点でのfirewalldの状態

前回の構築そのものを行った時点では、firewalldには以下のルールが存在しているかと思います。

  • 192.168.1.0/24からのポート80(HTTP):許可
  • 192.168.1.0/24からのポート443(HTTPS):許可
  • それ以外に、SSHやSambaなど別途ポートを許可しているものもあるが、ここではWebサーバーと関係ないので省略

この時点では「サーバー条件」の「IPアドレス」の図のように192.168.5.0/24のネットワークを新しく増設しても、ファイアウォールを開けていないので、アクセスできないはずです。

ここではクライアント(192.168.5.2)からWebサーバーへアクセスするには、Webサーバーの増設側のポートのIPアドレス、https(ポートは443)://192.168.5.1/ を入力すればわかります。もちろん192.168.5.0/24側のクライアントからはメイン側(192.168.1.18)へはアクセスできないことは、ネットワークの構造から明白です。

192-168-5-0_443-refuse.png

ほらね(* ॑꒳ ॑* )⋆*

firewalldでネットワークセグメントごとのアクセスを許可・拒否

別のネットワークセグメントのアクセスを許可

では192.168.5.0/24でも許可できるよう、Webサーバーのfirewalldに「192.168.5.0/24からのポート443(HTTPS):許可」というルールを追加してみます

ネットワークセグメントを特定して、特定のポートを受信ルールを入力するには、firewalldのrich ruleを使います。--add-rich-ruleを用いて、'~'で囲ったルールを入れます。

# firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.5.0/24" port port="443" protocol="tcp" accept'

これは、IPファミリーがIPv4、送信元が192.168.5.0/24で、ポート443、プロトコルがTCPで、受信を許可する、という意味です。

both.png

  • 192.168.1.0/24からのポート80(HTTP):許可
  • 192.168.1.0/24からのポート443(HTTPS):許可
  • 192.168.5.0/24からのポート443(HTTPS):許可

上の画面は、このfirewalldの状態でアクセスしてみた画面です。確かにアクセスできるようになりました!!ちなみに永続的にアクセスできるようにするには「firewall-cmd」の後ろに「--permanent」というオプションを付ければ、再起動してもルールが適用されたままになります

ちなみにネットワーク192.168.5.0/24からのポート443(HTTPS)の許可をやめたい場合は、--remove-rich-ruleを使って、'~'で囲った中に、削除したいルールを入れます。例えば、IPv4で、192.168.5.0/24からの、ポート443(TCP)の許可しているルールを削除するには、

# firewall-cmd --remove-rich-rule='rule family="ipv4" source address="192.168.5.0/24" port port="443" protocol="tcp" accept'

を入力すると、192.168.5.0/24からポート443(HTTPS)にアクセスできなくなります。

192-168-5-0_443-refuse.png

ネットワークアクセスを拒否するには

次にこんな実験をしてみた。メインのネットワークセグメント192.168.1.0/24で、ポート80(HTTP)を拒否し、ポート443(HTTPS)のみを許可した状態にしたままにする場合には、IPv4、192.168.1.0/24からのポート80(TCP)の許可というルールを、削除します

# firewall-cmd --remove-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="80" protocol="tcp" accept'

基本的にfirewalldはルールに存在しないものはデフォルトで遮断しているので、ルールにリストアップされていない場合は受信拒否する(ホワイトリスト方式)ので、上記みたいに、192.168.1.0/24のポート80がルールにない場合は、アクセス不可能になります。

port80refuse.png

Apacheの所有者とパーミッションの管理

Apacheの起動ユーザーを変更する

Apacheではデフォルトでは(ソースをコンパイルしてインストールした場合)、ユーザーは「daemon」で起動しています。ps -auxで、全部のプロセスとユーザー一覧を表示し、うち「httpd」を起動しているものに絞るので、grep httpdで絞り込みます

# ps -aux | grep httpd
root     14249  0.0  0.4 121504 17124 ?        Ss   17:15   0:00 /usr/local/apache2/bin/httpd -k start
daemon   14250  0.0  0.5 1332460 22448 ?       Sl   17:15   0:00 /usr/local/apache2/bin/httpd -k start
daemon   14251  0.0  0.4 1330220 19460 ?       Sl   17:15   0:00 /usr/local/apache2/bin/httpd -k start
daemon   14252  0.0  0.6 1332460 25428 ?       Sl   17:15   0:00 /usr/local/apache2/bin/httpd -k start
admin    17782  0.0  0.0   8176   924 pts/0    S+   18:45   0:00 grep --color=auto httpd

上記のように「/usr/local/apache2/bin/httpd」というhttpd本体を動かしているユーザーは「daemon」というデフォルトのユーザーになります。

では、明示的にApacheを動かすユーザーが決まっている場合、どのようにして編集すればいいのかどうかは、httpd.confを編集して対応しました。例えば、Apacheをユーザー名「apache」、グループ名「users」で実行したい場合(openSUSEで実験したので、グループ名はusersになっています)は、

# vi /usr/local/apache2/conf/httpd.conf
httpd.conf
Userdaemon   ← apacheに置き換える
Groupdaemon   ← usersに置き換える

それで、Apacheを再起動して、再度ps -auxで、httpdを動かしているユーザーを確認すると、

# ps -aux | grep httpd
root     17985  0.0  0.4 121504 16944 ?        Ss   18:48   0:00 /usr/local/apache2/bin/httpd -k start
apache   17986  0.0  0.3 1327980 16060 ?       Sl   18:48   0:00 /usr/local/apache2/bin/httpd -k start
apache   17987  0.0  0.2 1327980 11980 ?       Sl   18:48   0:00 /usr/local/apache2/bin/httpd -k start
apache   17988  0.0  0.2 1327980 11980 ?       Sl   18:48   0:00 /usr/local/apache2/bin/httpd -k start
admin    18071  0.0  0.0   8176   836 pts/0    S+   18:48   0:00 grep --color=auto httpd

ちゃんとapacheがhttpdを実行していることがわかります。

Apache上のファイルを他人に見せたい場合と見せたくない場合

Apacheの実行ユーザーを変更できたので、今度は他人に見せるものと見せたくないものを管理したいと思います。

他人のユーザーで、読み込みを許可しない場合

例えば、ApacheのhtdocsフォルダがWebで公開されているものとして、そこには以下のファイルが存在しているものとしましょう。

# cd /usr/local/apache2/htdocs
# ls -l
合計 24
-rw-r--r-- 1 admin users  304  6月 25 17:43 connect.php
-rw-r--r-- 1 root  root    45  6月 12  2007 index.html
-rw-r--r-- 1 root  root    20  6月 25 17:14 phpi.php
-rw-r--r-- 1 root  root   172  6月 25 18:52 some.html
-rw-r--r-- 1 admin users  346  6月 25 17:45 test_form.html
-rw-r--r-- 1 admin users 1271  6月 25 17:45 test_form.php

そこで「some.html」にアクセスするとしましょう。所有者はroot、パーミッションは644なので、この設定は、誰でも「some.html」はアクセスできるという意味で、ちなみにApacheでのアクセスは読み込みだけを行い、書き込みも実行も行わないので、読み込むだけにします。

200ok.png

Apacheの実行ユーザーはapacheにしました。その際はちゃんと読み込めました

そこで所有者はrootのままで「some.html」のパーミッションを600(他のユーザーは読み込み不可能にし、ユーザー自身だけが使用できる状態にする)に変更してみたんです

# chmod 600 some.html
# ls -l
合計 24
-rw-r--r-- 1 admin users  304  6月 25 17:43 connect.php
-rw-r--r-- 1 root  root    45  6月 12  2007 index.html
-rw-r--r-- 1 root  root    20  6月 25 17:14 phpi.php
-rw------- 1 root  root   172  6月 25 18:52 some.html
-rw-r--r-- 1 admin users  346  6月 25 17:45 test_form.html
-rw-r--r-- 1 admin users 1271  6月 25 17:45 test_form.php

403.png

今度はForbiddenになって読み込めなくなった。Apacheの実行ユーザーapacheがrootのものを読み込むことを禁止されているから明らかだね。

Apache実行ユーザーと同じ所有者で、他人のユーザーを許可しないファイル

今度は、さきほどの「some.html」を、Apache実行ユーザーの所有にし、他のユーザーが読み込めない状態のままに変更して、再度アクセスできるか調べます。

# chown apache:users some.html
# ls -l
合計 24
-rw-r--r-- 1 admin  users  304  6月 25 17:43 connect.php
-rw-r--r-- 1 root   root    45  6月 12  2007 index.html
-rw-r--r-- 1 root   root    20  6月 25 17:14 phpi.php
-rw------- 1 apache users  172  6月 25 18:52 some.html
-rw-r--r-- 1 admin  users  346  6月 25 17:45 test_form.html
-rw-r--r-- 1 admin  users 1271  6月 25 17:45 test_form.php

200ok.png

今度はアクセスできた( ´ •̥ ̫ •̥ ` )
自分自身のファイルだから読み込めるんだね

Apache実行ユーザーと同じ所有者で、どのユーザーを許可しないファイル

最後に、誰も読み込みを許可しないhtmlで検証してみます
「some.html」パーミッションは000か200に設定し、誰も読み込みを許可しない設定にします

# chmod 200 some.html
# ls -l
合計 24
-rw-r--r-- 1 admin  users  304  6月 25 17:43 connect.php
-rw-r--r-- 1 root   root    45  6月 12  2007 index.html
-rw-r--r-- 1 root   root    20  6月 25 17:14 phpi.php
--w------- 1 apache users  172  6月 25 18:52 some.html
-rw-r--r-- 1 admin  users  346  6月 25 17:45 test_form.html
-rw-r--r-- 1 admin  users 1271  6月 25 17:45 test_form.php

403.png

そうだよね( ˙꒳​˙ᐢ )
自分自身も読み込みを許可していないと、確かにこうなるしね…

まとめ

ミドルウェアを構築しました、その次に来るのは、誰が何を許可・拒否するかの取り決めを行うことが重要視されるので、Qiitaにはぜひとも基本としてfirewalldと所有者でアクセス権を設定する方法もここに載せておきたかったです

高度なセキュリティ分野だと暗号化も入るらしいが、ファイルシステムの暗号化に踏み切ると、パスワードの入力や鍵のタイミングについても複雑な課題が入ってしまうので、初心者にはあまりお勧めできないかな…と思ったので、必要最低限の暗号化についてはHTTPSの構築方法にとどめておいた( ´ •̥ ̫ •̥ ` )

まぁ大事なのは、漏れては問題となるようなものを置かない、不要なポートを開放しない、それが絶対的な前提だしね…

参照サイト

DockerでPHP7.4の環境を整えるときの問題点

DockerでPHP7.4の環境構築をした話

結論

PHPのDockerfileに以下を加える。

  • DockerでPHP7.4系以上を使うなら、ライブラリの「oniguruma」を入れる。
    • apk add --update --no-cache oniguruma-dev \を追記。
  • DockerでMySQLを使うなら、PDOドライバーを入れよう。
    • RUN docker-php-ext-install -j$(nproc) pdo_mysqlを追記。

対象

  • DockerでPHP7.4以上を使いたい方
  • DockerでMySQLの環境構築。

環境

  • OS Catalina 10.15.4
  • Docker 2.3.0.3
    • php:7.4.7-fpm-alpine

はじめに

phpenvを用いてPHPのバージョン管理すればDockerなんて使わなくていいやと思ってたんですけど、MySQLを使うことになって、5.7と8.0系で認証方式が違うとか、8.0系の方が早いとかありますけど、手元のpcにbrewで8.0から5.7系にダウングレードしたらお手上げ状態になったので、勉強も兼ねてサクッとDockerを使いました。
なお、Dockerfileのことしか主にコードを開示していないので、その他は頑張ってください。

問題点

  • php:7.4.0*の環境のDockerfileをビルド時No package 'oniguruma' foundとエラー。
  • PHPでMySQLを使おうとするとcould not find driverというエラー

上記にもあげている通り、2点問題に遭遇しました。

php:7.4.*の環境のDockerfileをビルド時No package 'oniguruma' foundとエラーがでよる。

はい、1つ目です。

Dockerfile
RUN apk upgrade --update\
&& apk add --update--no-cache oniguruma-dev \ # ここ&& apk --no-cache --virtual .build-deps add make g++ gcc re2c autoconf \
  && apk --no-cache add gettext-dev libzip-dev curl-dev \
  && docker-php-ext-install -j$(nproc) gettext mbstring zip opcache ctype json bcmath sockets curl \
  && pecl channel-update pecl.php.net

php:7.4以降はlibonig-devというパッケージをする必要があるそうです。

https://github.com/kkos/oniguruma

上記2行目にも書いていますが、apk add --update --no-cache oniguruma-devを追加して下さい。

PHPでMySQLを使おうとするとcould not find driverというエラー

はい、2つ目です。

このエラーが出ている人はphpinfo();をphpファイルに書いてもらって、
PDO項目を見ていただくとわかると思うのですが、sqliteしかないと思われます。

つまりデフォルトでドライバーはsqliteしか入ってないらしいですね。

これには少しびっくりしたと同時に、コンテナは本当に最小限のものしか入ってないのかと思いましたね。。。

というわけでDockerfileに書きましょう。

Dockerfile
# PDO driver(MySQL)RUN docker-php-ext-install -j$(nproc) pdo_mysql

先ほどと同じDockerfileに追記してください。

buildした後、phpinfo();のPDO driver項目を見るとmysqlが追加されていると思います。

他のデータベースを使いたい時も同じようにDockerfileにかけば使用できるでしょう。

PHPのDockerfile全体

今回はUbuntuでなく、Alpineで軽量化しています。
なので皆さんご存知のapt-getは使えず、apkというパッケージマネージャを使う必要があります。

Dockerfile
FROM php:7.4.7-fpm-alpineARG environmentRUN apk upgrade --update\
&& apk add --update--no-cache oniguruma-dev \
&& apk --no-cache--virtual .build-deps add make g++ gcc re2c autoconf \
&& apk --no-cache add gettext-dev libzip-dev curl-dev \
&& docker-php-ext-install -j$(nproc) gettext mbstring zip opcache ctype json bcmath sockets curl \
&& pecl channel-update pecl.php.net

# PDO(MySQL)RUN docker-php-ext-install -j$(nproc) pdo_mysql

まとめ

  • PHP7.4以上を使う場合はパッケージonigurumaをDockerfileに追記する。
  • PDOドライバーはデフォルトでsqliteしか入ってない。

参考

Browsing Latest Articles All 54 Live