たくあんメモ

だいたいプログラム開発のメモです

Amazon Linux 2に PHP 8.1をインストール

はじめに

Amazon Linux 2にPHPをインストールするときは、
いつもamazon-linux-extras リポジトリからインストールするのですが、
現時点では同リポジトリではPHP 8.1をインストールできません。

$ amazon-linux-extras list | grep php
 42  php7.4                   available    [ =stable ]
 51  php8.0                   available    [ =stable ]

ということで、今回はyumでインストールします。 

参考

今回は以下の記事を参考にしました。
https://computingforgeeks.com/how-to-install-php-centos-rhel-linux/

手順

1. remiリポジトリ追加

sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum install yum-utils

2. PHP 8.1インストール

sudo yum-config-manager --disable 'remi-php*'
sudo yum-config-manager --enable remi-php81
sudo yum install php81
sudo yum install php81-php-{cli,fpm,mysqlnd,zip,devel,gd,mbstring,curl,xml,pear,bcmath,json,opcache,redis,memcache,intl}

ただし、このままだと php コマンドでは使えないので、

$ php -v
-bash: php: コマンドが見つかりません
$ php81 -v
PHP 8.1.3 (cli) (built: Feb 16 2022 01:00:24) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.3, Copyright (c), by Zend Technologies

シンボリックリンクを貼る。

$ sudo ln -s /usr/bin/php81 /usr/bin/php
[ec2-user@ip-172-31-8-78 ~]$ php -v
PHP 8.1.3 (cli) (built: Feb 16 2022 01:00:24) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.3, Copyright (c), by Zend Technologies

このやり方でいいんかな??

phpbrewでPHP 8.1をインストール

はじめに

自分の場合はローカルPCでphpbrewを利用してPHPをインストールしています。

昨年PHP 8.1がリリースされたので、phpbrewでPHP 8.1をインストールします。

※phpbrew github.com  

手順

たぶん以下の手順でいけるかと。

  • phpbrew self-update
  • phpbrew known --update
  • phpbrew install 8.1.3 +default +dbs +curl +zlib
  • phpbrew switch 8.1.3

すでにphpbrewをインストール済みの場合は、 最初にphpbrew self-updateしてからphpbrew known --updateしないと8.1系が出てこないかもです。

また、PCにデフォルトでインストールされているopensslのバージョンが古い場合は、 opensslの最新バージョンをインストールしたうえで、 phpbrew installのオプションに +openssl=/usr/local/Cellar/openssl@x.x/x.xx などして、 opensslのパスを指定する必要になると思います。

以下のようなかんじで、無事にローカル環境をphp 8.1.3に切り替えました。

$ phpbrew switch php-8.1.3
$ php -v
PHP 8.1.3 (cli) (built: Mar  9 2022 23:04:47) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies


PHP 8.1に切り替えて発生するようになったエラーなど

個人的によく使っているCodeigniterはPHP 8.1に対応しているので問題なかったが、
他のライブラリでは以下のようなエラーが出るようになりました。

1. During inheritance of Countable: Uncaught ErrorException:

Fatal error:  During inheritance of Countable: Uncaught ErrorException: Return type of class::method() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /path/to/****.php:xxxx

これについては下記の記事が分かりやすかったので参考にしました。

scrapbox.io

2. strtolower(): Passing null to parameter #1 ($string) of type string is deprecated

某ライブラリが発せられている警告なのですが、、 せめて null チェックはしていて欲しかった。
組み込み関数のnullableでない引数にnullを渡すと、PHP 8.1から警告が出るようになります。 www.php.net

メンテされていないライブラリを使用しているからなのですが、
これを機に古いライブラリの入れ替えもしようかなと。

Markedカスタマイズ:テーブルの1列目をヘッダーにする

概要

Markedを使ってマークダウンでテーブルの1列目をthタグにするために、独自構文+renderメソッドで関数定義することで対応した。

Markedとは

マークダウン構文で書いたテキストをhtmlに変換するjavascriptライブラリ。

github.com

簡単な使い方

  • マークダウンを記述(タグにid属性を書いておく)
  • jsを読み込む
  • maked関数でid指定して、htmlに変換して表示する
<div id="markdownText">
|   | A | B | C | D | E | F | G | H  |
|---|---|---|---|---|---|---|---|----|
| A | H | F | E | C | D | G | B | A! |
| B | - | H | D | A | C | D | E | B! |
| C | - | - | H | F | G | B | A | C! |
| D | - | - | - | H | B | E | G | D! |
| E | - | - | - | - | H | A | F | E! |
| F | - | - | - | - | - | H | C | F! |
| G | - | - | - | - | - | - | H | G! |
| H | - | - | - | - | - | - | - | -  |
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
document.getElementById("markdownText").innerHTML = marked(document.getElementById("markdownText").innerHTML.trim());
</script>

実行すると、以下のような表示になる。

A B C D E F G H
A H F E C D G B A!
B - H D A C D E B!
C - - H F G B A C!
D - - - H B E G D!
E - - - - H A F E!
F - - - - - H C F!
G - - - - - - H G!
H - - - - - - - -

htmlは以下のようなソースになっている。

<div id="markdownText"><table>
<thead>
<tr>
<th></th>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
<th>E</th>
<th>F</th>
<th>G</th>
<th>H</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>H</td>
<td>F</td>
<td>E</td>
<td>C</td>
<td>D</td>
<td>G</td>
<td>B</td>
<td>A!</td>
</tr>
(中略)
</tbody></table>
</div>

1列目をthタグにしたい

マークダウンで1列目をthタグにするような構文は定義されていない?ので、Markedのrendererメソッドで拡張した。

1: 独自構文の定義

Markedのrendererメソッドでは "n列目" という情報を取得できなかったので、
まずは独自の構文を定義した。

以下のように、先頭に「:」をつけたものをthタグでレンダリングする事とする。

|   | A | B | C | D | E | F | G | H  |
|---|---|---|---|---|---|---|---|----|
| :A | H | F | E | C | D | G | B | A! |
| :B | - | H | D | A | C | D | E | B! |
| :C | - | - | H | F | G | B | A | C! |
| :D | - | - | - | H | B | E | G | D! |
| :E | - | - | - | - | H | A | F | E! |
| :F | - | - | - | - | - | H | C | F! |
| :G | - | - | - | - | - | - | H | G! |
| :H | - | - | - | - | - | - | - | -  |

2: Markedのrendererメソッドで拡張

詳しくはドキュメントを参照するとして、renderer.tablecellでテーブルセルの生成をカスタマイズ可能。 marked.js.org

あとはrenderer.tablecellメソッドで、
tbodyのセルのときだけテキストの先頭1文字目が「:」の場合は、thタグで生成するように書く。

var renderer = new marked.Renderer();

// tableタグ
renderer.tablecell = function(content, flags) {
    //thead
    if (flags.header) {
        return '<th>' + content + '</th>';
    }
    //tbody
    if (content.substring(0, 1) === ':') {
        return '<th>' + content.substring(1) + '</th>';
    }
    return '<td>' + content + '</td>';
};
marked.setOptions({
    renderer: renderer,
});

document.getElementById("markdownText").innerHTML = marked(document.getElementById("markdownText").innerHTML.trim());

これを実行すると以下のような表示になる。

ABCDEFGH
AHFECDGBA!
B-HDACDEB!
C--HFGBAC!
D---HBEGD!
E----HAFE!
F-----HCF!
G------HG!
H--------

生成するhtmlは以下のようになっており、1列目がthタグになっている。

<div id="markdownText"><table>
<thead>
<tr>
<th></th>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
<th>E</th>
<th>F</th>
<th>G</th>
<th>H</th>
</tr>
</thead>
<tbody><tr>
<th>A</th>
<th>H</th>
<th>F</th>
<th>E</th>
<th>C</th>
<th>D</th>
<th>G</th>
<th>B</th>
<th>A!</th>
</tr>
(中略)
</tbody></table>
</div>

 

※html/js全文

<div id="markdownText">
|   | A | B | C | D | E | F | G | H  |
|---|---|---|---|---|---|---|---|----|
| :A | H | F | E | C | D | G | B | A! |
| :B | - | H | D | A | C | D | E | B! |
| :C | - | - | H | F | G | B | A | C! |
| :D | - | - | - | H | B | E | G | D! |
| :E | - | - | - | - | H | A | F | E! |
| :F | - | - | - | - | - | H | C | F! |
| :G | - | - | - | - | - | - | H | G! |
| :H | - | - | - | - | - | - | - | -  |
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
var renderer = new marked.Renderer();

// tableタグ
renderer.tablecell = function(content, flags) {
    //thead
    if (flags.header) {
        return '<th>' + content + '</th>';
    }
    //tbody
    if (content.substring(0, 1) === ':') {
        return '<th>' + content.substring(1) + '</th>';
    }
    return '<td>' + content + '</td>';
};
marked.setOptions({
    renderer: renderer,
});

document.getElementById("markdownText").innerHTML = marked(document.getElementById("markdownText").innerHTML.trim());
</script>

phpbrewでPHP 8をインストール

はじめに

2020/11/26にPHP 8がリリースされました。 www.php.net

自分の場合はローカルPCでphpbrewを利用して、
複数バージョンのPHPをインストールしています。

というわけで今回もphpbrewでPHP 8をインストールすることに。

※phpbrew github.com  

手順

たぶん以下の手順でいけるかと。

  • phpbrew self-update
  • phpbrew known --update
  • phpbrew install 8.0.0 +default +dbs +curl
  • phpbrew switch 8.0.0

すでにphpbrewをインストール済みの場合は、 最初にphpbrew self-updateしてからphpbrew known --updateしないと8.0.0が出てこないです。

また、PCにデフォルトでインストールされているopensslのバージョンが古い場合は、 opensslの最新バージョンをインストールしたうえで、 phpbrew installのオプションに +openssl=/usr/local/Cellar/openssl@x.x/x.xx などして、 opensslのパスを指定する必要になると思います。

以下のようなかんじで、ローカル環境をphp 8.0.0に切り替えました。

php -v         
PHP 8.0.0 (cli) (built: Nov 28 2020 08:39:49) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies

Codeigniter 4はまだPHP 8に対応してないらしく、、、
php spark serve でローカルサーバが起動しないっす。

% php spark serve       
PHP Deprecated:  Required parameter $userAgent follows optional parameter $body in [path]/vendor/codeigniter4/framework/system/HTTP/IncomingRequest.php on line 161

Deprecated: Required parameter $userAgent follows optional parameter $body in [path]/vendor/codeigniter4/framework/system/HTTP/IncomingRequest.php on line 161


CodeIgniter CLI Tool - Version 4.0.3 - Server-Time: 2020-11-29 09:21:52am

An uncaught Exception was encountered

Type:        ErrorException
Message:     Required parameter $from follows optional parameter $verbs
Filename:    [path]/vendor/codeigniter4/framework/system/Router/RouteCollection.php
Line Number: 1018

8対応してくれるのを待つしかない。。

1582年10月4日の翌日は?

はじめに

以下の記事を読んでたら、
1582/10/4の翌日がほんとに1582/10/15になるかを確かめたくなった。 nagise.hatenablog.jp

というわけで、今回はJavaPHPPythonとGoで検証をしてみた。

結論

Javaのみ1582/10/4の翌日が1582/10/15になった。
PHPPythonとGoは1582/10/4の翌日が1582/10/5だった。  

各言語のバージョン

ローカルPCに入っているバージョンを使用。*1

検証

Java

Javaは内部的にユリウス暦グレゴリオ暦の切り替えを行っている。

ソースコード

import java.util.Calendar;
import java.util.GregorianCalendar;

public class Test {

    public static void main(String[] args) {
        Calendar c = Calendar.getInstance();
        
        c.set(1582, Calendar.OCTOBER, 4);
        System.out.println(c.getTime());
        c.add(Calendar.DAY_OF_MONTH, 1);
        System.out.println(c.getTime());
        
        System.out.println("---------------");
        
        c = new GregorianCalendar(1582, Calendar.OCTOBER, 4);
        System.out.println(c.getTime());
        c.add(Calendar.DAY_OF_MONTH, 1);
        System.out.println(c.getTime());
    }

}

実行

$ java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
$ 
$ javac Test.java
$ java Test
Thu Oct 04 03:29:27 JST 1582
Fri Oct 15 03:29:27 JST 1582
---------------
Thu Oct 04 00:00:00 JST 1582
Fri Oct 15 00:00:00 JST 1582

PHP

Javaみたいなことはやってない。

ソースコード

format('Y-m-d')."\n";
echo $date->modify('+1 days')->format('Y-m-d')."\n";

実行

$ php -v
PHP 7.4.0 (cli) (built: Mar 14 2020 18:05:20) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
$ 
$ php Test.php 
1582-10-04
1582-10-05

Python

Javaみたいなことはやってない。

ソースコード

import datetime

date = datetime.date(1582, 10, 4)
print(date)
print(date + datetime.timedelta(days=1))

実行

$ python3 --version
Python 3.7.5
$
$ python3 Test.py 
1582-10-04
1582-10-05

■Go

Javaみたいなことはやってない。

ソースコード

package main

import(
    "fmt"
    "time"
)

func main() {
    t := time.Date(1582, 10, 4, 0, 0, 0, 0, time.Local)
    fmt.Println(t)

    t = t.AddDate(0, 0, 1)
    fmt.Println(t)
}

実行

$ go version
go version go1.13 darwin/amd64
$ 
$ go run Test.go
1582-10-04 00:00:00 +0900 JCST
1582-10-05 00:00:00 +0900 JCST

終わりに

しょうもないことに時間を使ってしまったと反省。

なお、Perlでも試そうとしたが、CPANモジュールをインストールできなくて諦めた。
(また今度、気が向いたときにやる)

*1:最新バージョンじゃない

composerでCodeIgniter 4をインストール〜ローカルサーバ起動

はじめに

先日、CodeIgniter 4正式版がリリースされたので、
数年前から4devで作って運用していたものをそろそろ4正式版ベースに移行したいと思った。

CodeIgniter 4のインストール&セットアップ

インストール方法は公式サイトに何通りかのやり方を書いているが、 codeigniter4.github.io

今回は先にGithub上にリポジトリを作っていたので、

Adding CodeIgniter4 to an Existing Project
https://codeigniter4.github.io/userguide/installation/installing_composer.html#adding-codeigniter4-to-an-existing-project

の手順に沿ってインストールした。

$ composer require codeigniter4/framework
Using version ^4.0 for codeigniter4/framework
./composer.json has been created
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
    - codeigniter4/framework v4.0.2 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - codeigniter4/framework v4.0.1 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - codeigniter4/framework 4.0.0 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - Installation request for codeigniter4/framework ^4.0 -> satisfiable by codeigniter4/framework[4.0.0, v4.0.1, v4.0.2].

  To enable extensions, verify that they are enabled in your .ini files:
    - /Users/[myuser]/.phpbrew/php/php-7.4.0/etc/php.ini
    - /Users/[myuser]/.phpbrew/php/php-7.4.0/var/db/iconv.ini
  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.

Installation failed, deleting ./composer.json.

うぐっ。見慣れないエラーが。。

$ phpbrew ext install intl
===> Installing intl extension...
Log stored at: /Users/[myuser]/.phpbrew/build/php-7.4.0/ext/intl/build.log
Changing directory to /Users/[myuser]/.phpbrew/build/php-7.4.0/ext/intl
===> Phpize...
===> Configuring...
Error: Command failed: ./configure --with-php-config=/Users/[myuser]/.phpbrew/php/php-7.4.0/bin/php-config >> /Users/[myuser]/.phpbrew/build/php-7.4.0/ext/intl/build.log 2>&1 returns:

php-intlのインストールも失敗。いったい何事。。 というわけでログを調べる。

~/.phpbrew/build/php-7.4.0/ext/intl/build.log

checking if debug is enabled... no
checking if zts is enabled... no
checking for gawk... no
checking for nawk... no
checking for awk... awk
checking if awk is broken... no
checking whether to enable internationalization support... yes, shared
checking for icu-uc >= 50.1 icu-io icu-i18n... no
configure: error: Package requirements (icu-uc >= 50.1 icu-io icu-i18n) were not met:

No package 'icu-uc' found
No package 'icu-io' found
No package 'icu-i18n' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables ICU_CFLAGS
and ICU_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

icuとやらをインストールすればいいんかな(=よく分かっていない)。

icu4cをインストール

やってみたところ既にインストール済みだった。
念のためupgradeをして、ログの助言通りPKG_CONFIG_PATHを設定。

$ brew install icu4c
Updating Homebrew...
Error: icu4c 63.1 is already installed
To upgrade to 66.1, run `brew upgrade icu4c`
$ brew upgrade icu4c
(以下省略)
$ export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig"

気を取り直してphp-intlをインストール

$ phpbrew ext install intl
(中略)
[*] intl extension is enabled.
Done.

はい。いけました。(icu4cのupgradeは不要だったかもしれない)

改めてCodeIgniter 4をインストール

今度こそ!

まずはcomposerでインストール

$ composer require codeigniter4/framework
Using version ^4.0 for codeigniter4/framework
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 5 installs, 0 updates, 0 removals
  - Installing laminas/laminas-zendframework-bridge (1.0.3): Downloading (100%)         
  - Installing laminas/laminas-escaper (2.6.1): Downloading (100%)         
  - Installing psr/log (1.1.3): Downloading (100%)         
  - Installing kint-php/kint (3.3): Downloading (100%)         
  - Installing codeigniter4/framework (v4.0.2): Downloading (100%)         
kint-php/kint suggests installing kint-php/kint-twig (Provides d() and s() functions in twig templates)
kint-php/kint suggests installing kint-php/kint-js (Provides a simplified dump to console.log())
kint-php/kint suggests installing symfony/polyfill-mbstring (Replacement for ext-mbstring if missing)
kint-php/kint suggests installing symfony/polyfill-iconv (Replacement for ext-iconv if missing)
kint-php/kint suggests installing symfony/polyfill-ctype (Replacement for ext-ctype if missing)
Writing lock file
Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!

はい。いけました。

setup

これは公式ドキュメントに書いている通り、手動作業が必要。
https://codeigniter4.github.io/userguide/installation/installing_composer.html#set-up

ソースコードのコピー

rsync -arvz vendor/codeigniter4/framework/app ./
rsync -arvz vendor/codeigniter4/framework/public ./
rsync -arvz vendor/codeigniter4/framework/tests ./
rsync -arvz vendor/codeigniter4/framework/writable ./
cp -p vendor/codeigniter4/framework/env ./
cp -p vendor/codeigniter4/framework/phpunit.xml.dist ./
cp -p vendor/codeigniter4/framework/spark ./
cp -p vendor/codeigniter4/framework/.gitignore ./

※公式ドキュメントには書いてないけど、 .gitignore にいろいろ書かれているのでコピーしたほうがよいと思う。

systemディレクトリのパス修正

app/Config/Paths.php

public $systemDirectory = __DIR__ . '/../../vendor/codeigniter4/framework/system';

ローカルサーバ起動

4devのときは php serve.php でローカルサーバを起動していたのだが、
コマンドが変わっていた。

$ php spark serve

CodeIgniter CLI Tool - Version 4.0.2 - Server-Time: 2020-04-26 07:46:28am

CodeIgniter development server started on http://localhost:8080
Press Control-C to stop.
[Sun Apr 26 21:46:28 2020] PHP 7.4.0 Development Server (http://localhost:8080) started

spark”ってちょっとかっこいいな。

f:id:gayou:20200426214851p:plain

初回の画面も(4devに比べて)いいかんじになりました。

PHP 7.4:Trying to access array offset on value of type bool

概要

PHP 7.4にバージョンアップすると、
Trying to access array offset on value of type bool のExceptionが発生するようになった。

問題のコード

mb_regex_encoding("UTF-8");
mb_ereg_search_init($text, "[가-힣]+");
if (mb_ereg_search()) {
    $hangul = mb_ereg_search_getregs(); //get first result
    $text = str_replace($hangul[0], $hc->conv2kana($hangul[0], true), $text);
    do {
        $hangul = mb_ereg_search_regs();//get next result
        $text = str_replace($hangul[0], $hc->conv2kana($hangul[0], true), $text);  //←ここでエラーが発生
    } while($hangul);
}

これは何をやっているかというと、
$text からハングルの文字列を順に抜き出して、自前モジュールでカタカナに変換している。

do - whileの中で mb_ereg_search_regs関数で順に抜き出していって、
抜き出せるものがなくなったらループから抜ける。

このコードはphp 7.3までは特に問題なく動いていた。

何が問題か

エラーメッセージのとおり、
bool型の変数に対して配列インデックスでアクセスしているのが問題っぽい。

mb_ereg_search_regs() 関数は抜き出せるものがない場合、FALSEを返す仕様である。 https://www.php.net/manual/ja/function.mb-ereg-search-regs.php

do-whileの中でmb_ereg_search_regs() は最後はFALSEを返すので、
$hangul = FALSE; が代入され、

それに対して $hangul[0] のように配列アクセスをしているので今回のエラーが発生したようだ。

対処

とりあえずやっつけで回避。

mb_regex_encoding("UTF-8");
mb_ereg_search_init($text, "[가-힣]+");
if (mb_ereg_search()) {
    $hangul = mb_ereg_search_getregs(); //get first result
    $text = str_replace($hangul[0], $hc->conv2kana($hangul[0], true), $text);
    do {
        $hangul = mb_ereg_search_regs();//get next result
        if ($hangul !== false) {
            $text = str_replace($hangul[0], $hc->conv2kana($hangul[0], true), $text);
        }
    } while($hangul);
}

本当はそもそもdo-while構文を使わずに書き直した方がよいのでは、と思っているが。