【実務未経験 歓迎】PHPで「Hello World」してみよう [異世界転生(転職)・なろう]

## はじめに

どうもはじめまして、異世転生者のジツム・ミケイケンと申します。

ある日交差点で車に轢かれてしまった俺はエスイーエス？というよくわからない世界に飛ばされてしまった。
右も左もわからない俺だが、ここでの生活の為にHello Worldくらいは書けるようになっておこうと思う。

## 本記事の成果物 (コピペ用)

短い時間で解決したい方に配慮し、本記事で作成するプログラムの成果物を先に開示しておきます。 (省エネ)

```php:main.php
<?php

hello_world("Да не люблю я Кудзэ-куна!");
```

## 本編

ここからは、上記コードを実行できるように手順を書いていきます。

記事をスクロールしながらコピペさえすれば動作するよう書いていく為、手元で動かしたい方はやってみて下さい。

あと、最近筆者が

[時々ボソッとロシア語でデレる隣のアーリャさん](https://roshidere.com/)

にハマってしまった影響でサンプルコードにも影響が出ていますがご容赦下さい。

記事を読んでいくと前述のmain.phpのロシア語で何を言っているかが判明します。
(え？どうでもいいって？)

## 環境

- M1 Mac
- Docker Desktop
  - OS: debian 12

※これじゃないと動かないというよりも、筆者はこれで動きました的なやつです。

## 作成予定のファイル構成

ふんわりと、こんな感じのファイル作成するんだな〜と思いながら読み飛ばして下さい。

```
.
├── Dockerfile
├── Makefile
├── alya.cpp
├── alya.ini
└── main.php
```

## まずはDockerをInstallしましょう

以下のリンクからDockerをダウンロードできます。
環境によってはDockerは不要ですが、筆者はホストマシンでうまく動かなかったので利用しています。

https://www.docker.com/ja-jp/

DockerとはMacの中で別の異世界を作るようなものであるとイメージして下さい。

## 実行環境の立ち上げ

PHPのinstallが面倒くさいので、もともとPHPが入っているDocker imageを利用します。

https://hub.docker.com/_/php

```Dockerfile:Dcokerfile
FROM php:bookworm

WORKDIR /app

RUN alias gs='git status -s' ## 仮想環境の中で打つの辛いので

RUN apt update
RUN apt install -y git
RUN apt install -y vim ## lsp使えないのが辛い...
RUN apt install -y clang-format ## それでもformatはしたい
RUN git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP.git /tmp/PHP-CPP
RUN cd /tmp/PHP-CPP && \
    make && \
    make install
```

### 解説

PHPにはhello_worldという関数は存在しない為、関数自体を自作をしています。

```Dockerfile
## 具体的にはここから下の行です。

RUN git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP.git /tmp/PHP-CPP
```

急に出てきたリポジトリですが、紹介します。
[CopernicaMarketingSoftware/PHP-CPP](https://github.com/CopernicaMarketingSoftware/PHP-CPP)とはC++で[PHP extension](https://www.php.net/manual/ja/extensions.alphabetical.php)がコンパイルできるOSSで、本記事はこちらのライブラリがあって成立しています。


Extensionを作成するには他にもPHP FFIなどいくつかの方法があります。

- Zend Extension (ネイティブ実行)
- PHP Extension  (ネイティブ実行)
- PHP FFI        (ネイティブ+Zend VM実行)
- PHP Library    (Zend VM実行)

## Dockerfile作成後コンテナを立ち上げていく

```sh
## hogeという名前のImage作成
docker build -t hoge .
## fooという名前のコンテナを立ち上げ
docker run -itd -v $PWD:/app --name foo hoge
```

### 仮想環境の中に入る方法
```sh
docker exec -it foo bash
```

### 作成した環境の確認

あまり重要じゃないですが、環境差異でエラーになった方向けです。

```sh
## アーキテクチャ
arch
> aarch64

## OS
cat /etc/os-release
> PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
> NAME="Debian GNU/Linux"
> VERSION_ID="12"
> VERSION="12 (bookworm)"
> VERSION_CODENAME=bookworm
> ID=debian
> HOME_URL="https://www.debian.org/"
> SUPPORT_URL="https://www.debian.org/support"
> BUG_REPORT_URL="https://bugs.debian.org/"

## PHP
php -v
> PHP 8.3.9 (cli) (built: Jul 23 2024 06:02:10) (NTS)
> Copyright (c) The PHP Group
> Zend Engine v4.3.9, Copyright (c) Zend Technologies

## C++
c++ -v
> Using built-in specs.
> COLLECT_GCC=c++
> COLLECT_LTO_WRAPPER=/usr/lib/gcc/aarch64-linux-gnu/12/lto-wrapper
> Target: aarch64-linux-gnu
> Thread model: posix
> Supported LTO compression algorithms: zlib zstd
> gcc version 12.2.0 (Debian 12.2.0-14)
```

## コンパイルに必要なファイルを揃える

### Makefile

[公式ドキュメント](https://www.php-cpp.com/documentation/your-first-extension)にMakefileを作ってくれとあるので作成しちゃいます。

ファイルの中で変更可能箇所にコメントしてます。

```Makefile:Makefile
CPP             = g++
RM              = rm -f
CPP_FLAGS       = -Wall -c -I. -O2 -std=c++11

PHP_CONFIG      = $(shell which php-config)
LIBRARY_DIR		= $(shell ${PHP_CONFIG} --extension-dir)
PHP_CONFIG_DIR	= $(shell ${PHP_CONFIG} --ini-dir)

LD              = g++
LD_FLAGS        = -Wall -shared -O2
RESULT          = alya.so ## ここをお好みで変えても大丈夫

PHPINIFILE		= alya.ini ## ここをお好みで変えても大丈夫

SOURCES			= $(wildcard *.cpp)
OBJECTS         = $(SOURCES:%.cpp=%.o)

all:	${OBJECTS} ${RESULT}

${RESULT}: ${OBJECTS}
		${LD} ${LD_FLAGS} -o $@ ${OBJECTS} -lphpcpp

clean: ##
		${RM} *.obj *~* ${OBJECTS} ${RESULT}

${OBJECTS}:
		${CPP} ${CPP_FLAGS} -fpic -o $@ ${@:%.o=%.cpp}

## PHP Extension Dirへcp
install:
		cp -f ${RESULT} ${LIBRARY_DIR}/
		cp -f ${PHPINIFILE}	${PHP_CONFIG_DIR}/

## PHP Extension Dirからrm
uninstall:
		rm ${LIBRARY_DIR}/${RESULT}
		rm ${PHP_CONFIG_DIR}/${PHPINIFILE}
```

### alya.ini (php.ini)
PHPがロードする為のconfigファイルの作成を行います。
PHPINIFILEで定義しているので、makeで読み込まれます。

```ini:alya.ini
; 時々ボソッとロシア語でデレる隣のアーリャさん
extension=alya.so
```

### C++コード

以下のC++コードで、hello_world関数を定義しつつ``extern "C"``でCからC++を利用可能にしています。


[公式Doc](https://wiki.php.net/internals/extensions)にもありますが拡張機能は必ずget_moduleシンボルを提供する必要があります。

> PHP extensions must export a get_module symbol.

あ、あたし拡張機能の名称を「tun_dere(ツンデレ)」になんか、してないんだからね!! ///

```c++:alya.cpp
#include <iostream>
#include <phpcpp.h>

using namespace std;

Php::Value hello_world(Php::Parameters &params) {
    string v = params[0].stringValue();

    cout << v + " (久世君なんか好きじゃないもん)" << endl;

    return 0;
}

extern "C" {
PHPCPP_EXPORT void *get_module() {
    static Php::Extension extension("tun_dere", "1.0");

    extension.add<hello_world>("hello_world",
                               {
                                   Php::ByVal("string", Php::Type::String),
                               });

    return extension.module();
}
}
```

## ファイルが揃ったのでビルドしていく

### Build

書いたC++(C)コードをPHPで利用可能にするため、.soファイル(PHP拡張機能)を生成します。

```sh
make
> g++ -Wall -c -I. -O2 -std=c++11 -fpic -o alya.o alya.cpp
> g++ -Wall -shared -O2 -o alya.so alya.o -lphpcpp
```

``-o alya.so alya.o``となっているので、soファイルが作成されていそうです。

### /php/extensionsや/php/conf.dに各種ファイル設置

```sh
make install
> cp -f alya.so  /usr/local/lib/php/extensions/no-debug-non-zts-20230831/
> cp -f alya.ini 	/usr/local/etc/php/conf.d/
```

### 自作したPHP拡張機能がinstallされているか確認
```sh
php -m | grep -2 tun_dere
> standard
> tokenizer
> tun_dere ## <=== installされている！ 
> xml
> xmlreader
```

### おまけ (デバッグしたい場合)

```sh
make clean && make && make install

### 以下コマンドを打ったのと同じ
## make clean
## make
## make uninstall
## make install
```

## さあ「Hello World」をしてみよう！ (ついにタイトル回収)

main.phpというファイルを以下の内容で作成します。

```php:main.php
<?php
## ※アーリャはロシア語でデレます
hello_world("Да не люблю я Кудзэ-куна!");
```

作成したファイルを実行してみます。

```sh
## PHP 実行
php main.php
```

### 実行結果

![abc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170923/9573182f-508e-8e89-9800-1c7d3ab1ee21.png)

本当は俺のこと好きなくせにってやつですね(^o^)♪ <= ちょっとキモい

## まとめ
俺は無事に「ケイケンネンスウ・サンネンイジョウ」を手にして、異世界のんびりスローライフ生活を送れる日は来るのだろうか ...

---
次回予告: 受託開発国、闇の見積もり村編

※ 本記事はフィクションです。