BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル PHP 7 — class と interface の改善

PHP 7 — class と interface の改善

ブックマーク

キーポイント

  •  PHP 7 added anonymous classes for one-off objects; examples being a value object, and an object that implements an interface for dependency injection. 
  • Anonymous classes are designed for single-use and don't require full class definition. 
  • Anonymous classes are just like full-fledged classes and may extend other classes, implement interfaces, define constructor args, etc.
  • PHP 7 introduced the IntlChar class to access information about Unicode characters.
  • PHP 7 deprecated some features such as the PHP 4-style constructors.

原文(投稿日:2020/06/22)へのリンク

この一連のアーティクルでは、PHP 7の新機能について説明します。最初のアーティクルでは、環境を準備してPHP 7を紹介し、オブジェクト指向プログラミングに関連する新機能について説明しました。このアーティクルでは、PHPのクラスとインターフェースに加えられた改善について説明します。

無名クラス

多くの場合、短期間の使用では、本格的なクラスインスタンスの代わりに使い捨てオブジェクトを使用できます。

PHP 7.0では、1回の使用でもインスタンス化するのに便利な無名クラスのサポートが追加されました。無名クラスは、他のクラスを拡張したり、インターフェイスを実装したり、コンストラクター引数を定義したりできるという点で、本格的なクラスと同じです。

例として、サーバログのログメッセージを処理する無名クラスを作成します。anonymous.php スクリプトを作成し、ログメッセージを設定できる関数 setMsg(string $msg) を含むインターフェイス LogMsg を定義します。 さらに、getter/setter メソッド getLogMsg(): LogMsg と setLogMsg(LogMsg $logMsg) を使用してクラス ServerLog を作成し、ログメッセージを設定します。

<?php
interface LogMsg {
	public function setMsg(string $msg);
}
class ServerLog {
	private $logMsg;
	public function getLogMsg(): LogMsg {
     	return $this->logMsg;
	}
	public function setLogMsg(LogMsg $logMsg) {
         $this->logMsg = $logMsg;
	}
}
$serverLog = new ServerLog;
$serverLog->setLogMsg(new class implements LogMsg {
	public function setMsg(string $msg) {
    	echo $msg;
	}
});
var_dump($serverLog->getLogMsg());
?>

 

ServerLog クラスのインスタンスを作成し、無名クラスとして指定された引数を使用して setLogMsg(LogMsg $logMsg) 関数を呼び出します。

$serverLog = new ServerLog;
$serverLog->setLogMsg(new class implements LogMsg {
	public function setMsg(string $msg) {
    	echo $msg;
	}
});

スクリプトを実行すると、var_dumpSetLogMsg に渡した無名クラスオブジェクトへの参照を出力します。

object(class@anonymous)#2 (0) { }

前の例で無名クラスを使用していなかった場合、LogMsg インターフェイスを実装する完全なクラス定義を提供する必要がありました。無名クラスを使用しない同じ例は次のようになります。

<?php
interface LogMsg {
	public function setMsg(string $msg);
}
class ServerLogMsg implements LogMsg {
	public function setMsg(string $msg) {
    	echo $msg;
	}
}
class ServerLog {
	private $logMsg;
	public function getLogMsg(): LogMsg {
     	return $this->logMsg;
	}
	public function setLogMsg(LogMsg $logMsg) {
         $this->logMsg = $logMsg;
	}
}
$serverLog = new ServerLog;
$serverLog->setLogMsg(new ServerLogMsg());
var_dump($serverLog->getLogMsg());
?>

同じ無名クラス宣言からインスタンス化されたすべてのオブジェクトは、まさにそのクラスのインスタンスであり、互いに同一です。=== 演算子を使用して同一性の比較が実行されます。これは、比較されるオブジェクトが等しく、同じタイプであることを意味します。=== を使用した同一性比較は、最初は混乱するように見える可能性があります。等式演算子 == から始めましょう。2つのオブジェクトインスタンスは、同じ属性と値を持ち、同じクラスのインスタンスである場合、(==演算子で比較して) 等しくなります。同一性比較演算子 (===) を使用すると、2つのオブジェクトは、同じクラスの同じインスタンスを参照する場合にのみ同一となります。

これを理解するには、スクリプト anonymous-class-objects.php を作成し、無名クラスオブジェクトを返す関数を定義します。ここで、get_class 関数を使用して、関数を2回呼び出してインスタンス化された2つの異なる無名オブジェクトのクラスの名前を取得し、それらの名前を比較します。以下に示すように、2つの名前は等しくなります。

<?php
function a_class()
{
	return new class {};
}
 
if(get_class(a_class())===get_class(a_class())){
echo "Objects are instances of same class ".get_class(a_class());
}else{
echo "Objects are instances of different classes";
}
echo "</br>";
var_dump(get_class(a_class()));
echo "</br>";
var_dump(get_class(a_class()));
?>

スクリプト anonymous-class-objects.php を実行すると、オブジェクトが同じクラスのインスタンスであり、名前が class@anonymous. で始まることを示す出力メッセージが生成されます。同じインスタンス class@anonymousC:\PHP7.4\php-7.4-ts-windows-vc15-x64-r6c9821a\scripts\sumints.php000000000626B031 が返されます。これは、2つの同一のクラスを示します。無名クラス名はPHPエンジンによって割り当てられ、実装に依存します。

Objects are instances of same class class@anonymousC:\PHP7.4\php-7.4-ts-windows-vc15-x64-r6c9821a\scripts\sumints.php000000000626B031
string(98) "class@anonymousC:\PHP7.4\php-7.4-ts-windows-vc15-x64-r6c9821a\scripts\sumints.php000000000626B031"
string(98) "class@anonymousC:\PHP7.4\php-7.4-ts-windows-vc15-x64-r6c9821a\scripts\sumints.php000000000626B031"

無名クラスは、extendsを使用して別のクラスを拡張することもできます。

これを示すために、スクリプト anonymous-extend-class.php を作成し、フィールド $msg とクラス LogMsg を定義し、そのフィールドの関数を get/set します。ここで、関数 getLogMsg(): LogMsg および setLogMsg(LogMsg $logMsg) とクラス ServerLog を作成します。最後に、ServerLog クラスのインスタンスを作成し、別のクラス LogMsg を拡張する無名クラスとして提供された LogMsg 引数を使用して setLogMsg(LogMsg $logMsg) 関数を呼び出します:

$serverLog = new ServerLog;
$serverLog->setLogMsg(new class extends LogMsg {
	public function setMsg(string $msg) {
        $this->msg = $msg;
	}
});

anonymous-extend-class.php スクリプトは次のとおりです:

<?php
class LogMsg {
private $msg;
	public function getMsg() {
        return  $msg;
	}
}
class ServerLog {
	private $logMsg;
	public function getLogMsg(): LogMsg {
     	return $this->logMsg;
	}
	public function setLogMsg(LogMsg $logMsg) {
         $this->logMsg = $logMsg;
	}
}
$serverLog = new ServerLog;
$serverLog->setLogMsg(new class extends LogMsg {
	public function setMsg(string $msg) {
        $this->msg = $msg;
	}
});
var_dump($serverLog->getLogMsg());
?>

スクリプトを実行し、出力を確認します。LogMsg タイプの msg フィールドが NULL に設定されていることがわかります。

object(class@anonymous)#2 (1) { ["msg":"LogMsg":private]=> NULL }

想像どおり、引数を無名クラスのコンストラクタに渡すことができます。

これを示すために、スクリプト anonymous-extend-class-add-constructor.php を作成し、前の例のようにクラス LogMsgServerLog を定義します。唯一の違いは、引数が無名クラスコンストラクタに渡されることです:

$serverLog->setLogMsg(new class('Log Message') extends LogMsg {
	public function __construct($msg)
	{
        $this->msg = $msg;
	}
…
}

anonymous-extend-class-add-constructor.php スクリプトは次のとおりです。

<?php
class LogMsg {
private $msg;
	public function getMsg() {
        return  $msg;
	}
}
class ServerLog {
	private $logMsg;
	public function getLogMsg(): LogMsg {
     	return $this->logMsg;
	}
	public function setLogMsg(LogMsg $logMsg) {
         $this->logMsg = $logMsg;
	}
}
$serverLog = new ServerLog;
$serverLog->setLogMsg(new class('Log Message') extends LogMsg {
	public function __construct($msg)
	{
        $this->msg = $msg;
	}
	public function setMsg(string $msg) {
        $this->msg = $msg;
	}
});
var_dump($serverLog->getLogMsg());
?>

スクリプトを実行し、無名クラスコンストラクタに渡され、getLogMsg() によって返されるログメッセージが出力されることを確認します。

object(class@anonymous)#2 (2) { ["msg":"LogMsg":private]=> NULL ["msg"]=> string(11) "Log Message" }

無名クラスは別のクラス内にネストできますが、外部クラスの保護された関数またはプライベート関数またはプロパティを使用することはできません。無名内部クラスで外部クラスのプライベートプロパティを使用するには、前の例に示すように、プロパティを引数として無名クラスコンストラクターに渡します。

これを示すために、スクリプト inner-class-private.php を作成し、プライベートプロパティのある外部クラス Outer を定義します。無名クラスオブジェクトを返す関数 inner() を追加します。Outer クラスのプライベートプロパティは、無名クラスコンストラクタに渡され、無名クラスのプライベートプロパティとして設定されます。これで、無名クラスで宣言された関数を使用して、Outer クラスから無名内部クラスに渡されたプライベートプロパティ値を返すことができます:

return new class($this->a) extends Outer {
             private $a;
            public function __construct($a)
        	{
                $this->a = $a;
        	}
            public function getFromOuter()
        	{
                echo $this->a;
        	}
    	};

Outer から無名内部クラスに渡されたプライベートプロパティ値を出力するには、Outer クラスのインスタンスを作成し、無名クラスを作成する関数 inner() を呼び出し、プライベートプロパティの値を返す無名クラス関数を呼び出します:

echo (new Outer)->inner()->getFromOuter();

inner-class-private.php スクリプトは次のとおりです。

<?php
class Outer
{
	private $a = 1;
	public function inner()
	{
    	  return new class($this->a) {
             private $a;
            public function __construct($a)
        	{
                $this->a = $a;
        	}
        	            public function getFromOuter()
        	{
          	  echo $this->a;	
        	}
    	  };
	}
}
echo (new Outer)->inner()->getFromOuter();
?>

スクリプトを実行し、Outer から内部クラスに渡されたプライベートプロパティ値 (1) がブラウザに出力されることを確認します。

次に、Outer クラスの保護された関数を無名クラスで呼び出す方法を示します。外部クラスで定義された保護された関数の1つを呼び出すには、無名の内部クラスが前者を拡張する必要があります。

これを示すために、スクリプト inner-class-protected.php を作成し、保護されたフィールドと保護された関数を使用して Outer という外部クラスを定義します。ここで、Outer クラスを拡張する無名クラスを作成する別の関数を定義し、この無名クラスに、最初に定義した外部クラス保護関数を呼び出す関数を定義させます。無名クラスが Outer クラスを拡張すると、保護されたフィールドと関数が継承されます。これは、this を使用して保護された関数とフィールドにアクセスできることを意味します。無名クラス関数は、最初に Outer クラスのインスタンスを作成することにより、以前と同様に呼び出されます:

echo (new Outer)->inner()->getFromOuter();

inner-class-protected.php スクリプトは次の通りです。

<?php
class Outer
{
	protected $a = 1;
	protected function getValue()
	{
    	return 2;
	}
	public function inner()
	{
    	return new class extends Outer {
            public function getFromOuter()
        	{
                echo $this->a;
                echo "<br/>";
                echo $this->getValue();
        	}
    	};
	}
}
echo (new Outer)->inner()->getFromOuter();
?>

スクリプトを実行し、出力される Outer クラスの保護フィールドと Outer クラスの関数によって返される値を確認します。

1

2

2つの例を使用しました。1つは外部クラスからプライベートフィールドを呼び出す方法を示し、もう1つはネストされた無名クラスの外部クラスから保護されたフィールドと関数を呼び出す方法を示します。次のように、無名のネストされたクラスで外部クラスを拡張して外部クラスの保護されたフィールドと関数を継承し、外部クラスのプライベートフィールドを無名クラスのコンストラクタに渡すことで、2つの例を組み合わせることができます。

return new class($this->prop) extends Outer {
…
}

これを示すために、スクリプト script inner-class.php を作成します。

<?php
class Outer
{
	private $prop = 1;
	protected $prop2 = 2;
	protected function func1()
	{
    	return 3;
	}
	public function func2()
	{
    	return new class($this->prop) extends Outer {
            private $prop3;
            public function __construct($prop)
        	{
                $this->prop3 = $prop;
        	}
            public function func3()
        	{
                return $this->prop2 + $this->prop3 + $this->func1();
        	}
    	};
	}
}
echo (new Outer)->func2()->func3();
?>

スクリプトを実行すると、外部クラスのフィールドと関数を呼び出して取得した値 6 が出力されます。

Unicode 文字用の新しい IntlChar クラス

PHP 7.0では、Unicode 文字に関する情報にアクセスするためのいくつかのユーティリティメソッドを備えた IntlChar という新しいクラスが導入されました。IntlChar クラスを使用するには、Intl 拡張機能をインストールする必要があることに注意してください。これは、php.ini 構成ファイルの次の行のコメントを解除することで可能になります:

extension=intl

IntlChar クラスのいくつかのメソッドを表2に示します。

表2.  IntlChar のメソッド

メソッド

説明

IntlChar::charFromName

Unicode 文字のコードポイント値を名前で返します

IntlChar::charName

 Unicode 文字の名前を返します

IntlChar::charType

Unicode コードポイントの一般的なカテゴリ値を返します。たとえば、大文字と小文字のカテゴリは IntlChar::CHAR_CATEGORY_TITLECASE_LETTER です。10進数のカテゴリは IntlChar::CHAR_CATEGORY_DECIMAL_DIGIT_NUMBER です。文字が定義されたカテゴリのいずれにも含まれない場合、カテゴリは IntlChar::CHAR_CATEGORY_UNASSIGNED です

IntlChar::chr

コードポイント値で Unicode 文字を返します

IntlChar::getNumericValue

Unicode コードポイントの数値を返します

IntlChar::isdefined

文字が定義されているかどうかを示すブール値を返します

 

これらのメソッドのいくつかをテストするために、スクリプト Intlchar.php を作成します。次の例では、定数 IntlChar::UNICODE_VERSION を使用して Unicode バージョンを出力します。LATIN CAPITAL LETTER B の Unicode コードポイントを見つける。\u{00C6} が定義されているかどうかを確認します。スクリプト Intlchar.php を以下に示します。

<?php
printf('Unicode Version : ');
echo "<br/>";
echo IntlChar::UNICODE_VERSION;
echo "<br/>";
echo IntlChar::charFromName("LATIN CAPITAL LETTER B");
echo "<br/>";
var_dump(IntlChar::isdefined("\u{00C6}"));
 
?>

スクリプトを実行すると、次の出力が生成されます:

Unicode Version :
12.1
66
bool(true)

非推奨の機能

PHP 7は、多くの機能も廃止します。

PHP 7.0.xで廃止された機能の中には、PHP 4、「古い」スタイルのコンストラクタ、つまりクラスと同じ名前のコンストラクタメソッドがあります。

例として、スクリプト constructor.php を作成し、次のリストをコピーします。

<?php
class Catalog {
	function Catalog() {
	}
}
?>

スクリプトは、同じ名前の Catalog を使用するメソッドでクラス Catalog を宣言します。スクリプトを実行すると、次のメッセージが出力されます。

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; Catalog has a deprecated constructor

さらに、非静的メソッドへの静的呼び出しは PHP7.0.0 で非推奨になりました。これを実証するために、スクリプト static.php を作成し、それに非静的関数 getTitle() を持つクラスを宣言する次のリストをコピーして、その関数への静的呼び出しを試みます:

<?php
class Catalog {
	function getTitle() {
	}
}
Catalog::getTitle();
?>

このスクリプトを実行すると、次のメッセージが出力されます:

Deprecated: Non-static method Catalog::getTitle() should not be called statically

PHP 7.1.xでは、mcrypt 拡張機能は非推奨になりました。PHP 7.2の非推奨機能には、unquoted strings__autoload() メソッド、create_function()unset にキャスト、2番目の引数なしで parse_str() を使用、gmp_random() 関数、each() 関数、文字列引数付きの assert()、および read_exif_data() 関数が含まれます。PHP 7.3の非推奨機能には、大文字と小文字を区別しない定数、および名前空間での assert() の宣言が含まれます。

大文字と小文字を区別しない定数の非推奨の例として、次のスクリプトを実行します。このスクリプトでは、case_insensitive パラメータを true に設定して define() 関数を呼び出します:

<?php
define('CONST_1', 10, true); 
var_dump(CONST_1); 
var_dump(const_1);
?>

これにより、次のメッセージが表示されます:

Deprecated: define(): Declaration of case-insensitive constants is deprecated   on line 2

int(10)

Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "CONST_1"   on line 4

要約

PHP 7に関するシリーズのこの2番目のアーティクルでは、クラスとインターフェイスの新機能について説明しました。最も注目すべき新機能は、無名クラスのサポートです。Unicodeは、Unicode文字に関する情報を取得するために使用できる新しいクラス IntlChar によっても強化されます。

シリーズの次のアーティクルでは、PHP の型システムの新機能について説明します。

著者について

Deepak Vohra氏 は、Sun 認定の Java プログラマおよび Sun 認定の Web コンポーネント開発者です。Deepak Vohra氏は、Java および Java EE 関連の技術記事を WebLogic Developer's Journal、XML Journal、ONJava、java.net、IBM developerWorks、Java Developer’s Journal、Oracle Magazine、および devx に公開しています。Deepak Vohra氏は Docker に関する5冊の本を出版しており、Docker メンターです。Deepak Vohra氏は、PHP に関するいくつかの記事と、Ruby on Rails for PHP and Java Developers という本も公開しています。 

 

 

 

 

 

この記事に星をつける

おすすめ度
スタイル

こんにちは

コメントするには InfoQアカウントの登録 または が必要です。InfoQ に登録するとさまざまなことができます。

アカウント登録をしてInfoQをお楽しみください。

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

コミュニティコメント

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

BT