1. 記事一覧 >
  2. ブログ記事
category logo

php→pythonのトランスパイル

(更新) (公開)

はじめに

詳細は、別記事にありますが、ラズパイでBME280センサから取得した温湿度気圧をWeb画面に表示する wiringpi-php-bme280(GitHub) というのを作りました。
こちらは、phpのプログラムですが、同じことがpythonでもできるはずです。
そこで、まず、ツールでphp→pythonの変換をして、それを元に開発することにしました。今回は、変換ツール(トランスパイラ)の選定と試した結果を書きたいと思います。

【 トランスパイル(transpile) 】

トランスパイル(transpile)とは、あるプログラミング言語から他のプログラミング言語へと変換(翻訳)することです。変換ツールをトランスパイラと呼びます。JavaScriptのBabelが有名です。


結果総評
変換元プログラムについて
nicolasrod/php2python
danleyb2/php2python
taichino/php2py
おまけ php→js


特に言及が無いところでは、root権限で作業していますので、sudoは省略しています。


結果総評

ツールのインストールの仕方、実行方法の前に、結果を先出ししますと、以下になります。

リポジトリ結果
nicolasrod/php2python良いかもしれないが、変換後もツールに依存してしまい、今回の用途で求めていた結果と違う。※変換結果の動作は未検証
danleyb2/php2python変換の正確性がいまいち。インストールは簡単。
taichino/php2pypreg_matchなど関数は変換されない。インストールが難しい。関数以外完璧かというとそうでもない。

インストールに難儀しましたが、 taichino/php2py が良いかなという印象です。 phpの関数がそのままになるので、 nicolasrod/php2python と見比べながら変換していくと良いかもしれません。 ただ、これをもってしても結局かなりの人力が必要という印象です。正直 wiringpi-php-bme280 の場合、規模が小さいため、全部人力の方が早かったです。

変換元プログラムについて

以下のphpプログラムで試します。動作内容は、特に意味は無いです。

<?php
echo "hello\n";
$dir = "/path/to";
if( !preg_match( "/\/$/", $dir ) ) $dir .= "/";
echo $dir . "\n";

$a[] = "1";
$a[] = "2";
$a[] = "3";
$x = array();
foreach ( $a as $b ) {
        $x[] = $b . "b";
}

print_r( $x );

$i = 1;
if ($i == 0) {
    echo "i=0";
} elseif ($i == 1) {
    echo "i=1";
} elseif ($i == 2) {
    echo "i=2";
}

echo "\n";

switch ($i) {
    case 0:
        echo "i=0";
        break;
    case 1:
        echo "i=1";
        break;
    case 2:
        echo "i=2";
        break;
}

echo "\n";

class User
{
  public $name;
  private $nickname;
  public function __construct($name,$nickname){
    $this->name = $name;
    $this->nickname = $nickname;
  }
}

$user = new User('foo','bar');
echo $user->name;

これを変換した場合、以下のようになるのが期待されます。(人力変換です。試したツールでは実現しませんでした。)

#!/usr/bin/python
#-*- coding: utf-8 -*-
import re
print("hello\n", end='')
dir = "/path/to"
if not re.match("\/$", dir):
  dir+="/"
print(dir+"\n", end='')

a = []
a.append("1")
a.append("2")
a.append("3")

x = []
for b in a:
    x.append(b + "b")

print(x)

i = 1
if i==0:
  print("i=0", end='')
elif i==1:
  print("i=1", end='')
elif i==2:
  print("i=2", end='')

print("\n", end='')

if i==0:
  print("i=0", end='')
elif i==1:
  print("i=1", end='')
elif i==2:
  print("i=2", end='')

print("\n", end='')

class User:
  name=0
  nickname=0
  def __init__(self, name, nickname):
    self.name = name
    self.nickname = nickname

user = User("foo", "bar")
print(user.name, end='')

[実行結果]

# php sample.php
hello
/path/to/
Array
(
    [0] => 1b
    [1] => 2b
    [2] => 3b
)
i=1
i=1
# python3 sample.py
hello
/path/to/
['1b', '2b', '3b']
i=1
i=1

nicolasrod/php2python

ラズベリーパイOSに nicolasrod/php2python をインストールして使ってみます。

検証環境は、

Raspbian GNU/Linux 10 (buster)

Python 3.7.3

PHP 7.3.29-1

になります。


インストール

GitHubから php2python-master.zip をダウンロードします。
※ここでは、/home/pi に置くものとします。


phpインストール

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

# apt update
# apt upgrade

※以降基本的にYのため、-yを付けます。-y は、? [Y/n]: のようなときに自動的に y とするオプションです。

# apt install -y php

apt updateapt upgradeは必要無いかもしれませんが、

apt install -y php

E: いくつかのアーカイブを取得できません。apt-get update を実行するか --fix-missing オプションを付けて試してみてください。

のエラーになり、今回は必要でした。

※pythonは最初から入っていました。


Composerインストール

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

【 Composer 】

Composerとはphpのライブラリ管理システムです。Node.jsで言うnpm, yarn、Pythonで言うpipのようなものです。

https://getcomposer.org/download/ から composer.phar をダウンロードして、php2python-masterに配置し、インストールします。

※piユーザーで作業するものとします。

# su - pi
$ unzip php2python-master.zip
$ cp composer.phar php2python-master/
$ cd php2python-master
$ php composer.phar install

piユーザー(一般ユーザー)としている理由は、php composer.phar installで以下のようにroot権限でやらない方が良いと怒られるからです。(root権限でインストールはできます。)

Do not run Composer as root/super user! See https://getcomposer.org/root for details

Continue as root/super user [yes]?


php2pythonインストール

README.mdに書かれている通りで特に問題無かったです。

$ python3 -m pip install -r requirements.txt

動作確認結果

サンプルプログラム
/home/pi/php2python-master/sample/sample.php
があるとします。

$ python3 php2py.py --keep-ast ./sample
[+] Converting ./sample/sample.php...
[*] Done!

【 AST 】

--keep-astとすると、変換後の.pyファイルとともに、.astファイルも作成されます。ASTとは、Abstract syntax tree=抽象構文木、つまり、構文解析結果のことです。

変換結果を見てみます。

#!/usr/bin/env python3
# coding: utf-8
if '__PHP2PY_LOADED__' not in globals():
    import os, os.path, sys
    __compat_layer = os.getenv('PHP2PY_COMPAT', 'php_compat.py')
    if not os.path.exists(__compat_layer):
        sys.exit(f'[-] Compatibility layer not found in file {__compat_layer}. Aborting.')
    # end if
    with open(__compat_layer) as f:
        exec(compile(f.read(), '<string>', 'exec'))
    # end with
    globals()['__PHP2PY_LOADED__'] = True
# end if
import inspect
php_print("hello\n")
dir_ = "/path/to"
if (not php_preg_match("/\\/$/", dir_)):
    dir_ += "/"
# end if
php_print(dir_ + "\n")
a_[-1] = "1"
a_[-1] = "2"
a_[-1] = "3"
x_ = Array()
for b_ in a_:
    x_[-1] = b_ + "b"
# end for
print_r(x_)
i_ = 1
if i_ == 0:
    php_print("i=0")
elif i_ == 1:
    php_print("i=1")
elif i_ == 2:
    php_print("i=2")
# end if
php_print("\n")
for case in Switch(i_):
    if case(0):
        php_print("i=0")
        break
    # end if
    if case(1):
        php_print("i=1")
        break
    # end if
    if case(2):
        php_print("i=2")
        break
    # end if
# end for
php_print("\n")
class User():
    name = Array()
    nickname = Array()
    def __init__(self, name_=None, nickname_=None):


        self.name = name_
        self.nickname = nickname_
    # end def __init__
# end class User
user_ = php_new_class("User", lambda : User("foo", "bar"))
php_print(user_.name)

文法も関数も置き換わっています。
ただ独自定義の関数に置き換わっていて、nicolasrod/php2python一式必要なようです。
今回の用途ではやりすぎ感があるかなと思いました。

ちなみに、作法があるようで、エラーになり、実行はできませんでした。(実行できるようにする方法は調べていません。)

$ python3 sample/sample.py
Traceback (most recent call last):
  File "sample/sample.py", line 10, in <module>
    exec(compile(f.read(), '<string>', 'exec'))
  File "<string>", line 335, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'sample/php_compat.ini'

danleyb2/php2python

ラズベリーパイOSに danleyb2/php2python をインストールして使ってみます。

検証環境は、

Raspbian GNU/Linux 10 (buster)

Python 3.7.3

PHP 7.3.29-1

になります。


インストール

phpインストール

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

# apt update
# apt upgrade

※以降基本的にYのため、-yを付けます。-y は、? [Y/n]: のようなときに自動的に y とするオプションです。

# apt install -y php

※pythonは最初から入っていました。


php2pythonインストール

以下のエラーになり、README.mdに書かれている通りではうまくいきませんでした。

# pip install convert2php
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting convert2php
  Downloading https://files.pythonhosted.org/packages/30/ba/afea59e34b8493c3318251a299c4fbb4aa132981b1f1a29478a9b5f69183/convert2php-0.0.1.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-AFkqGP/convert2php/setup.py", line 9, in <module>
        with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
    IOError: [Errno 2] No such file or directory: '/tmp/pip-install-AFkqGP/convert2php/README.md'

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-AFkqGP/convert2php/

Ubuntu 20.04.2, pip 20.3.4(python 2.7) でも同じでした。

# curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
# python2 get-pip.py
# pip install convert2php
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Collecting convert2php
  Using cached convert2php-0.0.1.tar.gz (3.8 kB)
    ERROR: Command errored out with exit status 1:
     command: /usr/bin/python2 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-ro3dH9/convert2php/setup.py'"'"'; __file__='"'"'/tmp/pip-install-ro3dH9/convert2php/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-Uyfch2
         cwd: /tmp/pip-install-ro3dH9/convert2php/
    Complete output (5 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-ro3dH9/convert2php/setup.py", line 9, in <module>
        with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
    IOError: [Errno 2] No such file or directory: '/tmp/pip-install-ro3dH9/convert2php/README.md'
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

danleyb2/php2python/issuesに情報がありましたが、自動でダウンロードしているconvert2php-0.0.1.tar.gz内にREADME.mdが存在しないのが原因のようです。
以下が正解になります。

# pip install https://github.com/danleyb2/php2python/archive/master.zip
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting https://github.com/danleyb2/php2python/archive/master.zip
  Downloading https://github.com/danleyb2/php2python/archive/master.zip
     \ 20kB 5.4MB/s
Building wheels for collected packages: convert2php
  Running setup.py bdist_wheel for convert2php ... done
  Stored in directory: /tmp/pip-ephem-wheel-cache-VnJSCL/wheels/ec/a1/26/3518603e2419732350d90abad491b052d62a58a7b5a3266859
Successfully built convert2php
Installing collected packages: convert2php
Successfully installed convert2php-0.0.1

動作確認結果

サンプルプログラム
sample.php
があるとします。

# convert2php -s sample.php
INFO - Converting: sample.php. Output file will be: sample.py
INFO - # Remove opening and closing <?php
INFO - # convert $this-> to self.
INFO - # convert :: to .
INFO - # Process if statements
INFO - # Process else statements
INFO - # Process try statements
INFO - # Process catch statements
INFO - # delete all }
INFO - # delete namespace|require_once|include_once
INFO - # convert protected $var to self.var = None then move into __init__
INFO - # convert public|protected function to def
INFO - # add `self` to function signatures
INFO - # classes not children to extend `object`
INFO - # process child classes
INFO - # convert $ to ''
INFO - # convert ; to ''
INFO - # convert new to ''
INFO - # process foreach
INFO - Converted: sample.php. to: sample.py. { Go on, Proof Check :) }

エラー無く、変換されました。変換結果を見てみます。


echo "hello\n"
dir = "/path/to"
if( !preg_match( "/\//", dir ) ) dir .= "/"
echo dir . "\n"

a[] = "1"
a[] = "2"
a[] = "3"
x = array()
for b  in  a :
        x[] = b . "b"

print_r( x )

i = 1
if i == 0:
    echo "i=0"
    echo "i=1"
    echo "i=2"

echo "\n"

switch (i) {
    case 0:
        echo "i=0"
        break
    case 1:
        echo "i=1"
        break
    case 2:
        echo "i=2"
        break

echo "\n"

class User(object):
  private nickname
  def __init__(self,name,nickname):
    self.name = None
    self.name = name
    self.nickname = nickname

user = User('foo','bar')
echo user->name

文法は置き換わっていますが、関数はそのままでした。
他、文字列を繋げるドットがそのままだったり、switch文がそのままだったりします。基本的なところしか変換しないようです。

当然エラーになり、実行はできませんでした。

# python sample.py
 python sample.py
  File "sample.py", line 2
    echo "hello\n"
                 ^
SyntaxError: invalid syntax

taichino/php2py

CentOStaichino/php2py をインストールして使ってみます。

検証環境は、

CentOS 7.6.1810

boost_1_55_0

PHP 5.4.45

になります。

検証環境について

Raspbian GNU/Linux 10 (buster)Ubuntu 20.04.2 にてインストールを実施しましたが、phc (php compiler)のビルドで以下のエラーになり、やや古い環境のCentOS 7.6でやりなおしたら、最後まで行きました。

・Raspbian GNU/Linux 10 (buster)の場合

# cd /home/pi/phc
# ./configure
configure: error: cannot find Boost headers version >= 1.55.0
# apt install -y libboost-all-dev
# ./configure
checking for gc/gc_cpp.h... no
configure: error: in `/home/pi/phc':
configure: error: libgc required
See `config.log' for more details
# apt install -y libgc-dev
# ./configure
# make
./src/lib/Map.h:11:10: fatal error: boost/tr1/unordered_map.hpp: そのようなファイルやディレクトリはありません
 #include <boost/tr1/unordered_map.hpp>
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [Makefile:2734: src/ast_to_hir/AST_annotate.lo] エラー 1
make[2]: ディレクトリ '/home/pi/phc' から出ます
make[1]: *** [Makefile:3094: all-recursive] エラー 1
make[1]: ディレクトリ '/home/pi/phc' から出ます
make: *** [Makefile:1607: all] エラー 2

・Ubuntu 20.04.2の場合

# cd phc
# touch src/generated/* Makefile.in configure Makefile libltdl/aclocal.m4 libltdl/Makefile.in libltdl/configure libltdl/Makefile
# ./configure
configure: error: C compiler cannot create executable

⇒Boostが無い以前の問題。めんどくさそうな気がして中止。


インストール

GitHubから php2py-master.zip をダウンロードします。その他、php-5.4.45.tar.gzboost_1_55_0.tar.gzを配置します。
※ここでは、/home/admin に置くものとします。

READMEにあるhttp://www.phpcompiler.org/へアクセスしないでください。無くなっていて、変なサイトに繋がります。

phcは、GitHubのpbiggar/phcに有りますが、ウィルスが検出されて、ダウンロードできませんでした。(手順中ではgit cloneで取得しています。)


phpインストール

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

# tar zxf php-5.4.45.tar.gz
# cd php-5.4.45
# yum install libxml2-devel
# ./configure --enable-embed
# make && make install

php-5.4.45にしたのは、taichino/php2pyのリリース日時からphp5系だろうと推測しただけでどのバージョンが正解なのかは良く分かりません。また、phcのconfigure時、以下のように--enable-embedを付けてインストールするように促されるため、ソースコードをビルドすることにしました。

configure: WARNING:
*******************************************************************************
* It seems the PHP embed SAPI has not been installed.                         *
*                                                                             *
* You will be able to compile and run phc, but you will not be able to        *
* compile PHP scripts with phc.                                               *
*                                                                             *
* To install the PHP embed SAPI, follow the PHP installation instructions,    *
* but make sure to pass the --enable-embed option to the PHP configure        *
* script.                                                                     *
*******************************************************************************

Boostインストール

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

【 Boost 】

Boostとは、C++のライブラリ集になります。phcのビルドに必要です。

# cd /home/admin
# tar zxf boost_1_55_0.tar.gz
# cd boost_1_55_0
# ./bootstrap.sh --prefix=/usr/
# ./b2 install

phcのビルドで /usr/include/boost/tr1 が必要なようで、最新のboost_1_76_0ではインストールされませんでした。また、boost_1_55_0より古い場合は、phcのconfigureでエラーになります。


phcインストール

phc (php compiler)をビルドします。いろいろエラーになりましたので、省略せず、直しながらの手順になります。

# cd /home/admin/
# git clone https://github.com/pbiggar/phc.git
# cd phc
# touch src/generated/* Makefile.in configure Makefile libltdl/aclocal.m4 libltdl/Makefile.in libltdl/configure libltdl/Makefile
# ./configure
configure: error: cannot find the flags to link with Boost regex
# vi m4/php-embed.m4
LIBS="-lphp5 -L${PHP_INSTALL_PATH}/lib -R${PHP_INSTALL_PATH}/lib $LIBS"
LIBS="-lphp5 -L${PHP_INSTALL_PATH}/lib ${wl}-R${PHP_INSTALL_PATH}/lib $LIBS"
# ./autogen.sh
configure.ac:20: error: require Automake 1.15, but have 1.13.4

⇒configureの作り直しには成功したので、無視。

# ./configure
checking for gc/gc_cpp.h... no
configure: error: in `/home/admin/phc':
configure: error: libgc required

⇒--disable-gcを付けてconfigureやり直し。

# ./configure --disable-gc
# make
src/embed/optimize.cpp: 静的メンバ関数 ‘static Method_info* PHP::get_method_info(String*)’ 内:
src/embed/optimize.cpp:223:63: エラー: ‘zend_fcall_info* {aka _zend_fcall_info*}’ から ‘uint {aka unsigned int}’ への無効な変換です [-fpermissive]
  int result = zend_fcall_info_init (&fn, &fci, &fcic TSRMLS_CC);
                                                               ^
src/embed/optimize.cpp:223:63: エラー: cannot convert ‘zend_fcall_info_cache* {aka _zend_fcall_info_cache*}’ to ‘zend_fcall_info* {aka _zend_fcall_info*}’ for argument ‘3’ to ‘int zend_fcall_info_init(zval*, uint, zend_fcall_info*, zend_fcall_info_cache*, char**, char**)’
src/embed/optimize.cpp: メンバ関数 ‘virtual bool Internal_method_info::return_by_ref()’ 内:
src/embed/optimize.cpp:248:22: エラー: ‘struct _zend_function::<anonymous>’ has no member named ‘return_reference’
  return func->common.return_reference;
                      ^
src/embed/optimize.cpp:249:1: 警告: 制御が非 void 関数の終りに到達しました [-Wreturn-type]
 }
 ^
大域スコープ:
cc1plus: 警告: 認識できないコマンドラインオプション "-Wno-unused-local-typedef" です [デフォルトで有効]
make[2]: *** [src/embed/optimize.lo] エラー 1
make[2]: ディレクトリ `/home/admin/phc' から出ます
make[1]: *** [all-recursive] エラー 1
make[1]: ディレクトリ `/home/admin/phc' から出ます
make: *** [all] エラー 2
# vi src/embed/optimize.cpp
223行目
int result = zend_fcall_info_init (&fn, &fci, &fcic TSRMLS_CC);
int result = zend_fcall_info_init (&fn, 0, &fci, &fcic, NULL, NULL TSRMLS_CC);
# make
src/embed/optimize.cpp: メンバ関数 ‘virtual bool Internal_method_info::return_by_ref()’ 内:
src/embed/optimize.cpp:248:22: エラー: ‘struct _zend_function::<anonymous>’ has no member named ‘return_reference’
  return func->common.return_reference;
                      ^
src/embed/optimize.cpp:249:1: 警告: 制御が非 void 関数の終りに到達しました [-Wreturn-type]
 }
 ^
大域スコープ:
cc1plus: 警告: 認識できないコマンドラインオプション "-Wno-unused-local-typedef" です [デフォルトで有効]
make[2]: *** [src/embed/optimize.lo] エラー 1
make[2]: ディレクトリ `/home/admin/phc' から出ます
make[1]: *** [all-recursive] エラー 1
make[1]: ディレクトリ `/home/admin/phc' から出ます
make: *** [all] エラー 2
# vi src/embed/optimize.cpp
248行目
return func->common.return_reference;
#ifdef ZEND_ENGINE_2
	return (func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE);
#else
	return func->common.return_reference;
#endif
# make
# make install

これでインストール完了ですが、まだ問題があり、phcコマンドを起動するとエラーになります。

# phc
phc: error while loading shared libraries: libboost_regex.so.1.55.0: cannot open shared object file: No such file or directory

シンボリックリンクを作成します。

# ln -s /usr/lib/libboost_regex.so.1.55.0 /usr/local/lib/libboost_regex.so.1.55.0

ようやく問題がなくなりました。


動作確認結果

サンプルプログラム
/home/admin/php2py-master.zip
/home/admin/sample.php
があるとします。

# cd /home/admin
# unzip php2py-master.zip
# cd php2py-master
# cp ../sample.php .
# phc --dump-xml=ast sample.php > sample.xml
# chmod 755 ./php2py.php
# vi php2py.php
#!/usr/bin/php
#!/usr/local/bin/php
# ./php2py.php sample.xml > sample.py

エラー無く、変換されました。変換結果を見てみます。

#!/usr/bin/python
#-*- coding: utf-8 -*-
print("hello\n")
dir = "/path/to"
if !preg_match("/\/$/", dir):
  dir+="/"
print(dir+"\n")
a[] = "1"
a[] = "2"
a[] = "3"
x = []
for b in a:
  x[] = b+"b"
print_r(x)
i = 1
if i==0:
  print("i=0")
elif i==1:
  print("i=1")
elif i==2:
  print("i=2")
print("\n")
if i==0:
  print("i=0")
elif i==1:
  print("i=1")
elif i==2:
  print("i=2")
print("\n")

class User:
  name=0
  nickname=0
  def __init__(name, nickname):
    self.name = name
    self.nickname = nickname

user = User("foo", "bar")
print(user.name)

文法が書き換わり、echoprintに変わりました。ただ、preg_matchprint_r関数はそのままでした。
また、配列のa[] = "1"のところはそのままでは動作しません。 他、文字列を繋げるドットがプラス記号に変わっていたり、switch文がif文に変わっていたりするので、先ほどの、danleyb2/php2pythonよりは良いような気がします。

当然エラーになり、実行はできませんでした。

# python sample.py
  File "sample.py", line 5
    if !preg_match("/\/$/", dir):
       ^
SyntaxError: invalid syntax

おまけ php→js

Node.jsのnpmで babel-preset-php というphp→jsのトランスパイラがあります。pythonに変換では無いですが、こちらは、どうなのか、やってみました。

検証環境は、

Raspbian GNU/Linux 10 (buster)

node v10.24.0

npm 5.8.0

6.26.0 (babel-core 6.26.3)

になります。

# apt update
# apt install nodejs
# apt install npm
# mkdir sample
# cd sample
# npm init -y
# npm i -S babel-preset-php
# vi .babelrc
{
  "presets": ["php"]
}
# npm i -g babel-cli
# cp ../sample.php .
# babel sample.php -o sample.js
Error: Plugin 0 specified in "/home/pi/sample/node_modules/babel-preset-php/src/index.js" provided an invalid property of "default" (While processing preset: "/home/pi/sample/node_modules/babel-preset-php/src/index.js")
    at Plugin.init (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/plugin.js:131:13)
    at Function.normalisePlugin (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:152:12)
    at /usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:184:30
    at Array.map (<anonymous>)
    at Function.normalisePlugins (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:158:20)
    at OptionManager.mergeOptions (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:234:36)
    at /usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:265:14
    at /usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:323:22
    at Array.map (<anonymous>)
    at OptionManager.resolvePresets (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:275:20)

エラーになりました。 babel6系の場合、babel-preset-php 1.* 系を使わないといけないようです。(参考

# vi package.json
"babel-preset-php": "^2.0.0",
"babel-preset-php": "^1.0.0",

# rm -rf node_modules package-lock.json
# npm install
# babel sample.php -o sample.js

変換できましたので、sample.jsを見てみます。

echo("hello\n");
var dir = "/path/to";
if (!preg_match("/\\/$/", dir)) dir += "/";
echo(dir + "\n");
a.push("1");
a.push("2");
a.push("3");
var x = Array();

for (var b of Object.values(a)) {
    x.push(b + "b");
}

console.log(x);
var i = 1;

if (i == 0) {
    echo("i=0");
} else if (i == 1) {
    echo("i=1");
} else if (i == 2) {
    echo("i=2");
}

echo("\n");

switch (i) {
    case 0:
        echo("i=0");
        break;

    case 1:
        echo("i=1");
        break;

    case 2:
        echo("i=2");
        break;
}

echo("\n");

class User {
    constructor(name, nickname) {
        this.name = name;
        this.nickname = nickname;
    }

};

var user = new User("foo", "bar");
echo(user.name);

文法は置き換わりましたが、echoechoのままで、preg_matchがそのままです。関数の置き換えまでは、各言語で微妙な挙動の違いもありますし、厳しいと思われます。
実行すると、以下のエラーになりました。

# node sample.js
/home/pi/sample/sample.js:1
echo("hello\n");
^

ReferenceError: echo is not defined
    at Object.<anonymous> (/home/pi/sample/sample.js:1:1)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)
loading...