PHP 7.2.0 Alpha 1をインストールして新機能を試してみる

PHP 7.2系の初めてのAlpha版である PHP 7.2.0 Alpha 1がリリースされました。

http://php.net/archive/2017.php#id2017-06-08-2

さっそくソースファイルからインストールして新機能を試してみます。なお、ここではWebサーバなどは使わず、単純にコマンドラインで実行を確認します。OS は VirtualBox 上の Ubuntu Server 16.04.2 LTS を使用し、必要なソフトウェアは、都度 apt でインストールしています。(例えば sudo apt install clang libxml2-dev make

まず、PHP 7.2.0 Alpha 1 のソースファイルを取得します。

$ wget https://downloads.php.net/~pollita/php-7.2.0alpha1.tar.gz

ダウンロードしたファイルを展開し、展開したディレクトリに移動します。

$ tar zxf php-7.2.0alpha1.tar.gz
$ cd php-7.2.0alpha1

ソースファイルからビルドします。今回は試すだけなので ./configure のオプションには特に何も指定していません。

$ ./configure
$ make
$ make test
$ sudo make install

sudo make install 時にPEARに関する次のようなエラーメッセージが表示されましたが、今回の試用には影響は無いのでそのまま進めます。

Makefile:445: ターゲット 'install-pear-installer' のレシピで失敗しました
make[1]: *** [install-pear-installer] エラー 255
Makefile:448: ターゲット 'install-pear' のレシピで失敗しました
make: *** [install-pear] エラー 2

インストールが完了すると php コマンドが利用できるようになります。

$ php -v
PHP 7.2.0alpha1 (cli) (built: Jun 10 2017 15:27:20) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0-dev, Copyright (c) 1998-2017 Zend Technologies

では、PHP 7.2.0 の新機能を試してみます。

オーバーライドされたメソッドの型宣言の省略

PHP 7.1 ではメソッドをオーバライドする際、親クラスと子クラスの引数の型宣言を同じものにする必要がありましたが、PHP 7.2 では子クラスの引数の型宣言を省略することができるようになりました。

<?php
class Foo
{
    public function hello(string $str)
    {
        echo "{$str} in Foo.\n";
    }
}

class Bar extends Foo
{
    // PHP 7.1 では public function hello(string $str) と書く必要がありました
    public function hello($str)
    {
        echo "{$str} in Bar.\n";
    }
}

$bar = new Bar();
$bar->hello('Hello, PHP 7.2');    // Hello, PHP 7.2 in Bar. を表示

これにより、今までは親クラスのメソッドに型宣言を追加すると、すべての子クラスのメソッドを変更しなければなりませんでしたが、 PHP 7.2 では子クラスをひとつずつ変更することができるので、変更作業を段階的に行うことができます。

抽象メソッドのオーバーライドを許可

抽象(abstract)メソッドをオーバーライドできるようになりました。

<?php
abstract class Foo
{
    abstract function bar(string $x);
}

abstract class Bar extends Foo 
{ 
    abstract function bar(string $x): int; 
}

名前空間のリスト構文で最後のカンマを許可

名前空間をグループ化してリスト状に書く場合、最後にカンマを書けるようになりました。

<?php
// PHP 7.1 では use Foo\Bar\{ Foo, Bar }; と書く必要がありました
use Foo\Bar\{ Foo, Bar, };

リストの最後にカンマを許可する提案は、関数の引数やクラスのメンバ変数などの他の構文でもあったようですが、投票の結果、採用されたのは名前空間グループ化(use)だけだったようです。 https://wiki.php.net/rfc/list-syntax-trailing-commas

パーフェクトPHP (PERFECT SERIES 3)

パーフェクトPHP (PERFECT SERIES 3)

docs.hatenablog.jp

X-ChromeLogger-Dataヘッダを使うとWebブラウザのコンソールにログを出力できる

主なWebブラウザにはコンソール機能があるので、JavaScriptconsole.log('Hello, world.'); と書くとコンソールにログを出力することができます。

X-ChromeLogger-Data というHTTPヘッダを使うと、JavaScriptに限らず、サーバサイドのプログラミング言語からFirefoxのコンソールへログを出力することができます。次に示すのはPHPX-ChromeLogger-Dataヘッダを出力する例です。

<?php
$log = array();
$log[] = array(array('Hello, world.'), 'log');

$json = array(
    'columns' => array('log', 'type'),
    'rows' => $log
);
$data = base64_encode(utf8_encode(json_encode($json)));
header('X-ChromeLogger-Data: ' . $data);

FirefoxはこのHTTPヘッダを受け取ると、コンソールへデコードした文字列を出力します。

f:id:hontonodeai:20170109131354p:plain

出力するレベルは上記の例で示した log のほかに、infowarnerror を使うこともできます。

<?php
$log = array();
$log[] = array(array('Hello, world.'), 'log');    // ログレベルを出力
$log[] = array(array('Hello, world.'), 'info');   // メッセージレベルを出力
$log[] = array(array('Hello, world.'), 'warn');   // 警告レベルを出力
$log[] = array(array('Hello, world.'), 'error');  // エラーレベルを出力

なお、FirefoxX-ChromeLogger-Data をデフォルトでサポートしていますが、Chromeの場合はChrome Loggerという拡張機能をインストールする必要があります。

craig.is

Chrome Loggerでは各言語別のロギングライブラリも紹介しており、PHPのライブラリを使うと以下のように簡単に書くことができます。

<?php
include 'ChromePhp.php';
ChromePhp::log('Hello, world.');

X-ChromeLogger-Data はHTTPヘッダなので、XMLJSON、PDFなど、余計な文字列を出力するとフォーマットとして成り立たないデータを表示する場合でも気軽にデバッグに使えそうです。

PHPプログラムの複雑度をPHPMDで客観的に測定する

プログラムが複雑になると、テストケースが増大し、保守性が低くなることは直感的・経験的に理解できますが、この複雑度を客観的に測定する方法として循環的複雑度(Cyclomatic complexity)があります。PHPでは、PHPMDを使って循環的複雑度を測定することができます。

PHPMD - PHP Mess Detector

インストール

PHPMDをインストールする方法はいくつかありますが、ここでは簡単な phpmd.phar を利用する方法を紹介します。 phpmd.phar を利用するためには、最新版をダウンロードして実行権限を付与するだけです。

$ wget -c http://static.phpmd.org/php/latest/phpmd.phar -O phpmd
$ chmod +x phpmd

なお、 phpmd.phar を実行するためには php-bz2 が必要なので、インストールされていない場合はインストールしておきます。次に示すのは Ubuntu 16.04 で apt を使ってインストール例です。

$ sudo apt install php-bz2

以下を実行してPHPMDのバージョン情報が表示されることを確認します。

$ ./phpmd --version
PHPMD 2.5.0

複雑度の基準

それでは早速PHPMDで循環的複雑度を測ってみますが、その前に複雑度の基準について説明します。PHPMDのドキュメントによると、複雑度は以下のように分類されます。

数値 複雑度
1~4
5~7
8~10
11以上 非常に高い

https://phpmd.org/rules/codesize.html#cyclomaticcomplexity

デフォルトではPHPMDがレポートを出力する閾値は10となっているため、複雑度がそれより低い場合はレポートを出力しません。今回は確認のため複雑度が低くてもレポートを出力するようにPHPMDのルールセットを変更します。ルールセットを変更するには以下のようなXMLruleset.xml というファイル名で保存します。reportLevel を 1 に設定するのがポイントです。

<?xml version="1.0"?>
<ruleset
    xsi:nonamespaceschemalocation="http://pmd.sf.net/ruleset_xml_schema.xsd"
    xsi:schemalocation="http://pmd.sf.net/ruleset/1.0.0
                        http://pmd.sf.net/ruleset_xml_schema.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://pmd.sf.net/ruleset/1.0.0"
    name="PHPMD rule set">
    <rule ref="rulesets/codesize.xml/CyclomaticComplexity">
        <properties>
            <property value="1" name="reportLevel"></property>
        </properties>
    </rule>
</ruleset>

プログラム例

それでは簡単なプログラムを書いてPHPMDを実行してみます。まずは次のようなプログラムを sample1.php というファイル名で保存します。

<?php
function sample1()
{
    echo "Hello, world.";
}

次のコマンドを実行します。

$ ./phpmd sample1.php text ruleset.xml
sample1.php:2  The function sample1() has a Cyclomatic Complexity of 1. The configured cyclomatic complexity threshold is 1.

Cyclomatic Complexity of 1 が出力されたので、循環的複雑度は 1 であることが確認できます。

次に、プログラムを少し複雑にしてみます。次のようなプログラムを sample2.php というファイル名で保存します。

<?php
function sample2($format)
{
    if ($format === 'xml' || $format === 'text' || $format === 'html') {
        echo "valid format.";
    } else {
        echo "invalid format.";
    }
}
$ ./phpmd sample2.php text ruleset.xml
sample2.php:2 The function sample2() has a Cyclomatic Complexity of 4. The configured cyclomatic complexity threshold is 1.

Cyclomatic Complexity of 4 が出力されたので、循環的複雑度は 4 であることが確認できます。

このプログラムを in_array() を使うように変更してみます。

<?php
function sample2($format)
{
    if (in_array($format, array('xml', 'text', 'html'), true)) {
        echo "valid format.";
    } else {
        echo "invalid format.";
    }
}
$ ./phpmd sample2.php text ruleset.xml
sample2.php:2 The function sample2() has a Cyclomatic Complexity of 2. The configured cyclomatic complexity threshold is 1.

循環的複雑度は 2 になりました。つまり、先ほどの in_array() を使わないバージョンより、 in_array() を使うバージョンの方が循環的複雑度が小さくなったことを意味します。

まとめ

循環的複雑度はあくまで指標のひとつです。循環的複雑度が小さいプログラムの可読性や効率が必ず高いとは限りません。しかしながら、プログラムの複雑度を計測することで、プログラムを変更した後の複雑度の変化を客観的に知ることができます。プログラミングの際に循環的複雑度を意識し、計測することで、よりよいソフトウェアを開発するためのヒントになるのではないでしょうか。

新装版 達人プログラマー 職人から名匠への道

新装版 達人プログラマー 職人から名匠への道

docs.hatenablog.jp

PHP 7.1.0 正式版をインストールして新機能を試してみる

PHP 7.1.0 正式版がリリースされました。

http://php.net/releases/7_1_0.php

さっそくソースファイルからインストールして新機能を試してみます。なお、ここではWebサーバなどは使わず、単純にコマンドラインで実行を確認します。OS は VirtualBox 上の Ubuntu Server 16.04.1 LTS を使用し、必要なソフトウェアは、都度 apt でインストールしています。(例えば sudo apt install clang libxml2-dev make

まず、PHP 7.1.0 のソースファイルを取得します。

$ wget -O php-7.1.0.tar.gz http://jp2.php.net/get/php-7.1.0.tar.gz/from/this/mirror

ダウンロードしたファイルを展開し、展開したディレクトリに移動します。

$ tar zxf php-7.1.0.tar.gz
$ cd php-7.1.0

ソースファイルからビルドします。今回は試すだけなので ./configure のオプションには特に何も指定していません。

$ ./configure
$ make
$ make test
$ sudo make install

インストールに成功すると php コマンドが利用できるようになります。

$ php -v
PHP 7.1.0 (cli) (built: Dec  2 2016 14:02:04) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.1.0-dev, Copyright (c) 1998-2016 Zend Technologies

では、PHP 7.1.0 の新機能を試してみます。

nullable な型

関数の型宣言(PHP5ではタイプヒンティングとも呼ばれていました)で null を許容する仕組みが追加されました。nullable な型を使うためには型名の前に ? を付けます。

<?php
declare(strict_types=1);    // 確認のため厳密な型チェックを有効にする

// $flgはbool型かnull
// 戻り値はstring型かnull
function foo(?bool $flg): ?string 
{
    if (is_null($flg)) {
        return null;
    }

    return $flg ? 'Yes' : 'No';
}

echo foo(true);    // 文字列 'Yes' を返す
echo foo(false);   // 文字列 'No' を返す
echo foo(null);    // nullを返す
echo foo();        // エラー(Fatal error: Uncaught ArgumentCountError)
echo foo(1);       // エラー(Fatal error: Uncaught TypeError)

void 関数

値を返さない関数の戻り値の型にvoidを指定できるようになりました。

<?php
function foo(): void 
{
    echo 'Hello, world.';
}

[ ]構文による配列の分割代入

配列の各要素を変数に代入する際に、[]を使ってlist()よりも簡単に書けるようになりました。

<?php
$array = [1, 2, 3];
list($a, $b, $c) = $array;  // PHP7.0ではlist()で分割代入できる
[$a, $b, $c] = $array;      // PHP7.1では[]構文で分割代入できる

クラス内定数がアクセス権をサポート

クラス内の定数にアクセス権を指定できるようになりました。

<?php
class Foo 
{
    const BAR1 = 1;    // 指定しない場合はpublic

    private const BAR2 = 2;
    protected const BAR3 = 3;
    public const BAR4 = 4;
}

echo Foo::BAR1;    // 1を表示
echo Foo::BAR2;    // エラー(Fatal error: Uncaught Error)
echo Foo::BAR3;    // エラー(Fatal error: Uncaught Error)
echo Foo::BAR4;    // 4を表示

iterable 擬似型

関数の型宣言で配列かTraversableインターフェイスを実装したオブジェクトを示すiterableを指定できるようになりました。

<?php
function foo(iterable $ite): void 
{
    foreach ($ite as $value) {
        echo $value;
    }
}

foo([1, 2, 3]);    // 123を表示
foo('Hello');      // エラー(Fatal error: Uncaught TypeError)

複数の例外を1つのcatch構文で捕捉

複数の異なる型の例外を、1つのcatch構文で捕捉できるようになりました。

<?php
try {
   
} catch (ExceptionType1 | ExceptionType2 $e) {
    // 2つの例外 ExceptionType1 と ExceptionType2 を捕捉します
} catch (\Exception $e) {
   
}

list() におけるキーのサポート

配列をlist()を使って分割代入する際に、キーで指定できるようになりました。

<?php
$array = ['db_name' => 'test', 'db_user' => 'root', 'db_password' => 'secret'];

// 順番を気にすることなく、キーに紐づく変数に代入できる
list(
    'db_user' => $user,
    'db_password' => $password,
    'db_name' => $database
) = $array;

前述の[]による分割代入を利用するとより簡潔に書くことができます。

<?php
$array = ['db_name' => 'test', 'db_user' => 'root', 'db_password' => 'secret'];

// list()の代わりに[]を利用して分割代入
[
    'db_user' => $user,
    'db_password' => $password,
    'db_name' => $database
] = $array;

文字列のオフセットに負数をサポート

文字列の各文字に[]{}を使ってアクセスする際に、負数を使えるようになりました。

<?php
$string = 'Hello, world.';
$string[-1] = '!';  // 後ろから1文字目を '!' に上書き
echo $string;       // 'Hello, world!' を表示

パーフェクトPHP (PERFECT SERIES 3)

パーフェクトPHP (PERFECT SERIES 3)

docs.hatenablog.jp

Windows 10 の Bash on Ubuntu on Windows で Node.js/gulp/Sass を実行する

現在 Windows 10 Insider Preview で提供され、今夏に正式リリース予定の Bash on Ubuntu on Windows では Node.js を利用することができます。今回は Node.js をインストールし、さらに gulp と Sass を実行してみます。

なお、Bash on Ubuntu on Windows 自体のインストール方法は以下の記事を参照してください。今回利用したのは Windows 10 Insider Preview Build 14390 です。 docs.hatenablog.jp

Node.js

まず、Bash on Ubuntu on Windows を起動して次のコマンドを実行し、公式の Node.js の安定バージョンである4系をインストールします。

curl -sL https://deb.nodesource.com/setup_4.x | bash -
apt-get install -y nodejs

インストールが成功すると node コマンドが利用可能になります。次のコマンドで Node.js のバージョン番号を確認できます。

node -v

パッケージマネージャである npm コマンドも利用可能になります。次のコマンドで npm のバージョン番号を確認できます。

npm -v

Node.js は JavaScript の実行環境ですので、簡単な JavaScript のコードを実行して動作を確認してみます。以下のコードを hello.js というファイル名で保存します。

console.log('Hello, Node.');

コードが正しければ、次のコマンドを実行すると画面に「Hello, Node.」と出力されます。

node hello.js

gulp

次はタスクランナーである gulp をパッケージマネージャ npm でインストールします。次のコマンドを実行して、gulp のコマンドラインツールをインストールします。

npm install -g gulp-cli

インストールが成功すると gulp コマンドが利用可能になります。次のコマンドで gulp のバージョン番号を確認できます。

gulp -v

では、gulp で簡単なタスクを実行して動作を確認してみます。まず、適当なディレクトリで次のコマンドを実行します。今回は作業用にnodeという名前のディレクトリを作成しました。

npm init

いくつか質問されますが、今回はすべてデフォルト値のまま(すべてエンターキーを押して)、最後に Is this ok? (yes) と表示されるので yes と入力します。

さらに次のコマンドを実行します。

npm install --save-dev gulp

これで gulp を実行する環境ができました。以下のコードを gulpfile.js というファイル名で保存します。

var gulp = require('gulp');

gulp.task('default', function() {
    console.log('Hello, gulp.');
});

コードが正しければ、次のコマンドを実行すると画面に「Hello, gulp.」と出力されます。

gulp

Sass

最後は Sass をインストールします。次のコマンドを実行して、gulp のタスクとして実行できる gulp-sass をインストールします。

npm install --save-dev gulp-sass

では、簡単な Sass のコードを CSS に変換してみます。まず、Sass のファイルを保存するディレクトリと、変換後の CSS ファイルを保存するディレクトリを作成しておきます。今回は sass-src ディレクトリと sass-dest ディレクトリを作成しました。

mkdir sass-src sass-dest

次に、以下のコードを sample.scss というファイル名で sass-src ディレクトリに保存します。

$sample-color: #f00;
body {
  color: $sample-color;
}

次に、gulpfile.js ファイルを次のように書き換えます。

var gulp = require('gulp');
var sass = require('gulp-sass');

gulp.task('sass', function() {
    gulp.src('./sass-src/*.scss')
        .pipe(sass())
        .pipe(gulp.dest('./sass-dest'));
});

コードが正しければ、次のコマンドを実行すると sass-dest ディレクトリに sample.css というファイルが作成されます。

gulp sass

sample.css を見ると正しく CSS に変換されています。

body {
  color: #f00; }

このように、Bash on Ubuntu on Windows でも通常の Ubuntu にインストールするのとほぼ同じ操作で Node.js、gulp、Sass が利用できました。

Web制作者のためのSassの教科書 これからのWebデザインの現場で必須のCSSメタ言語

Web制作者のためのSassの教科書 これからのWebデザインの現場で必須のCSSメタ言語

Mozilla の新ブラウザエンジン Servo の Developer Preview を試してみる

Mozilla Research が開発する新しいブラウザエンジン Servo の Developer Preview がリリースされました。Servo はより良い並列性、モジュール性、セキュリティ、ハイパフォーマンスを目指して開発されており、次のような特徴があります。

現在は Mac OS XLinux 用のバイナリファイルが提供されているため、すぐに試すことができます。なお、今後は WindowsAndroid 用も提供される予定です。それでは Linux 用のバイナリファイルをダウンロードして起動してみます。OS は VirtualBox 上の Ubuntu Desktop 16.04 LTS を使用しました。

まず、公式サイトの Download Servo nightly build からダウンロードページへ移動し、Linux Build (64-bit) ボタンを押して最新のバイナリファイルをダウンロードします。

https://servo.org/

次のコマンドでダウンロードしたファイルを展開します。

tar zxf servo-latest.tar.gz

展開すると servo ディレクトリが作成されるので、その中のシェルスクリプト runservo.sh を実行します。

cd servo
./runservo.sh

起動が成功すると次のようなアプリケーションが起動します。デフォルトでいくつかのブックマークが表示されています。

f:id:hontonodeai:20160709143909p:plain

f:id:hontonodeai:20160709144331p:plain

今のところアドレスバーとタグの切り替え機能がある程度のシンプルなUIです。Alt + で前のページへ戻り、Alt + で次のページへ進みます。また、いくつか日本語ページを表示してみましたが、EUC-JPで書かれたHTMLを表示すると文字化けしてしまうようです。

将来的には、Gecko に Rust/Servo のコンポーネントが搭載される予定です。

主要ブラウザの音声合成API(Speech Synthesis API)対応状況

JavaScript でテキストを読み上げることのできる音声合成API(Speech Synthesis API)は、主要ブラウザの中では Google Chrome で最初に利用可能になりました。その後、他のブラウザでも実装が進み、現在は次のような状況になっています。

次に示すのは音声合成APIの簡単な利用例です。

<input id="speechText" type="text" value="Hello, world">
<input id="speechButton" type="button" value="開始">

<script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
<script>
$(document).ready(function() {
    $('#speechButton').on('click', function() {

        if (!window.speechSynthesis) {
            alert('Speech Synthesis API未対応です。');
            return;
        }

        var speech = new SpeechSynthesisUtterance();
        speech.text = $('#speechText').val();

        speechSynthesis.speak(speech);
    });
});
</script>

SpeechSynthesisUtterance のプロパティを設定することで音声をカスタマイズすることができます。

var speech = new SpeechSynthesisUtterance();
speech.text = $('#speechText').val();

speech.volume = 1.0;       // 音量: 0.0~1.0
speech.rate = 1.0;         // 速度: 0.1~10.0
speech.pitch = 1.0;        // 音程: 0.0~2.0
speech.lang = 'ja-JP';     // 言語

var voices = window.speechSynthesis.getVoices();
speech.voice = voices[0];  // 音声: ブラウザによって種類が異なります。 

speechSynthesis.speak(speech);

特に window.speechSynthesis.getVoices() は特徴的で、現状ではブラウザによって言語や性別の異なる音声が用意されています。例えば Google Chrome 51 では英語(Google US English)、ドイツ語(Google Deutsch)、フランス語(Google français)、日本語(Google 日本語)などの20種類が用意がされているため、上記の例では voices[0] から voices[19] まで利用できますが、Microsoft Edge では2種類の女性の日本語音声(Microsoft Ayumi Mobile - Japanese (Japan)Microsoft Haruka Mobile - Japanese (Japan))と1種類の男性の日本語音声(Microsoft Ichiro Mobile - Japanese (Japan))なので上記の例では voices[0] から voices[2] まで利用できます。