古いバージョンのPHPのポリモーフィズムのすべての規範による型ヒント

tl; dr要するに、この記事では、PHP 5.6(バージョン5.4より前)のバージョンでも、静的プログラミング言語と同様のコンパイラー動作を実現できる特性を作成します。 さらに、特性は入力だけでなく、出力パラメーターも検証します。 つまり、タイピングのヒントに完全に没頭しています。

Webアプリケーションで問題なくこの特性を接続して使用できます。





7.0より古いPHPバージョンのタイプヒンティング



PHPバージョン<7では、メソッド定義で、関数に提供されるデータ型と関数の出力データ型を記述できます。



ここではすべてが素晴らしいです。 尋ねたところ、判明した。



public function filterArray(array $arr, string $filterParameter, callable $filterCallback) : array
      
      





配列のフィルタールールを定義する必要があります。ラムダ関数を取得して作成し、その中にフィルタールールを定義しました。 そして、彼らは事前にそれが配列であり、整数ではないことを知って、 $ arrfilterArray()渡しました



突然文字列ではなくオブジェクトを$ filterParameterとして渡すと 、PHPはすぐに解析エラーを返します。 例えば、私たちはこれを注文しませんでした。



PHPバージョン5.6でのタイプヒンティング



ただし、PHPバージョン<5.6は、出力データ型の明示的な指定をサポートしていません。



 public function sortArray($arr, $filterParam) : array // <-   { // ... }
      
      





また、PHP <5.6は、 integerstringfloatなどの入力データ型としてプリミティブをサポートしません。



ただし、一部のタイプは、古いバージョンの言語でも指定できます。 たとえば、 arrayobject 、またはクラスのインスタンスのタイプのパラメーターを関数に渡すように指定できます。



 /** * Class ArrayForSorting *  ,   -    ,     . */ class ArrayForSorting { /** *   . * * @var array */ public $arrayForSorting; /** * @construct */ public function __construct($arrayForSorting) { $this->arrayForSorting = $arrayForSorting; } } /** * Class UserSortArray * ,      : , , . */ class UserSortArray { /** *   . * * @var object */ public $availableSortingMethods; /** *   . * * @param ArrayForSorting $sortArray   ,   . * * @throws UserSortArrayException       . */ public function insertSort(ArrayForSorting &$sortArray) { if (false === isset($availableSortMethods->insertMethod)) { throw new UserSortArrayException('Insert method for user array sort is not available.'); } return uasort($sortArray->arrayForSorting, $availableSortMethods->bubbleMethod); } }
      
      





元の問題



しかし、お願いします。 配列ではなく、たとえば関数にdoubleを渡す必要がある場合はどうすればよいですか?



また、プログラマーは、文字列、少なくとも配列、少なくとも任意のクラスのインスタンスを関数に簡単に渡すことができます。



この場合の解決策は簡単です。入力パラメーターと出力パラメーターの妥当性を毎回確認するだけです。



 class ArraySorter { public function sortArray(array &$sortArray, $userCallback) { //      , //        , //   false  - -1. if (false === $this->validateArray($sortArray)) { return []; } return uasort($sortArray, $userCallback); } private function validateArray($array) { if (!isset($array) || false === is_array($array)) { return false; } return true; } }
      
      





ただし、同じコードを何回書かなければならないか考えることすら怖いです。



 if (null !== $param && '' !== $param) { return false; //  [],  '',          //  throw new Exception(__CLASS__ . __FUNCTION__ . ": Expected integer, got sting"); }
      
      





この問題の明らかな解決策は、トレーにバリデーターを書き込むことです。これに続いて、すべてのタイプの入力パラメーターのチェックを委任します。 パラメーターが必要なタイプではない場合、パーサーはすぐに例外をスローします。



出力では、次のものが得られます。





トレイトは、インポートされた任意のクラスから呼び出すことができるという点で、保護されたメソッドに本質的に似ています。 ただし、継承とは異なり、できるだけ多くの特性をクラスに接続し、そのすべてのプロパティとメソッドを使用できます。



特性は、バージョン5.4.0以降のPHPで使用できます。



特性のソース全体
Z.Y. 具体的には、各プリミティブの検証を個別に記述したため、将来的には追加の検証ルールを使用して配列を特性に渡すことができます。 たとえば、整数の場合はmaxValueminValueisNaturalを検証でき、文字列の場合は空ではなく長さを検証できます。

 <?php namespace traits; /** * Trait Validator *   . */ trait Validator { /** *  . * * @param array $validationParams   . *  : '' => . *      'not_empty' --      * (.. ,   ,   ). *     : * [ * 'integer' => 123, * 'string not_empty' => 'hello world!', * 'array' => [ ... ], * ] * * @return bool true    . * * @throws \Exception        . */ public function validate($validationParams) { //   ,   . $this->validateArray($validationParams); foreach ($validationParams as $type => $value) { $methodName = 'validate' . ucfirst($type); //   validateInteger $isEmptinessValidation = false; if ('not_empty' === substr($type, -9)) { $methodName = 'validate' . ucfirst(substr($type, 0, -9)); $isEmptinessValidation = true; } if (false === method_exists($this, $methodName)) { throw new \Exception("Trait 'Validator' does not have method '{$methodName}'."); } //   true,   ,   . $this->{$methodName}($value, $isEmptinessValidation); } return true; } /** *  . * * @param string $string  . * @param bool $isValidateForEmptiness      . * * @return bool  . */ public function validateString($string, $isValidateForEmptiness) { $validationRules = is_string($string) && $this->validateForSetAndEmptiness($string, $isValidateForEmptiness); if (false === $validationRules) { $this->throwError('string', gettype($string)); } return true; } /** *   . * * @param boolean $bool  . * * @return bool  . */ public function validateBoolean($boolean, $isValidateForEmptiness = false) { $validationRules = isset($boolean) && is_bool($boolean); if (false === $validationRules) { $this->throwError('boolean', gettype($boolean)); } return true; } /** *  . * * @param string $array  . * @param bool $isValidateForEmptiness      . * * @return bool  . */ public function validateArray($array, $isValidateForEmptiness) { $validationRules = is_array($array) && $this->validateForSetAndEmptiness($array, $isValidateForEmptiness); if (false === $validationRules) { $this->throwError('array', gettype($array)); } return true; } /** *  . * * @param string $object  . * @param bool $isValidateForEmptiness      . * * @return bool  . */ public function validateObject($object, $isValidateForEmptiness) { $validationRules = is_object($object) && $this->validateForSetAndEmptiness($object, $isValidateForEmptiness); if (false === $validationRules) { $this->throwError('object', gettype($object)); } return true; } /** *   . * * @param string $integer  . * @param bool $isValidateForEmptiness      . * * @return bool  . */ public function validateInteger($integer, $isValidateForEmptiness) { $validationRules = is_int($integer) && $this->validateForSetAndEmptiness($integer, false); if (false === $validationRules) { $this->throwError('integer', gettype($integer)); } return true; } /** *       ,   . * * @param string $parameter  . * @param bool $isValidateForEmptiness     (, , )  . * * @return bool  . */ private function validateForSetAndEmptiness($parameter, $isValidateForEmptiness) { $isNotEmpty = true; if (true === $isValidateForEmptiness) { $isNotEmpty = false === empty($parameter); } return isset($parameter) && true === $isNotEmpty; } /** *  . * * @param string $expectedType * @param string $gotType * * @throws \Exception      . */ private function throwError($expectedType, $gotType) { $validatorMethodName = ucfirst($expectedType) . 'Validator'; // integer -> IntegerValidator throw new \Exception("Parse error: {$validatorMethodName} expected type {$expectedType}, got {$gotType}"); } }
      
      









使用される特性は非常に単純です。 例として、この特性を使用して関数の入出力データをチェックする方法を示すために、一意の識別子を生成および取得するメソッドを格納するNotebookクラスを実装します。

 namespace models; use traits; /** * Class Notebook *    . */ class Notebook { use \traits\Validator; /** *  ID . * * @var string */ private $_uid; /** * @construct */ public function __construct() { $this->_uid = $this->generateUniqueIdentifier(); } /** *   ID . * * @return string */ public function getNotebookUID() { //  validate()        //   'primitiveName' => $primitiveValue. //          , //     . $this->validate([ 'string not_empty' => $this->_uid, ]); return $this->_uid; } /** *   ID . * * @return string */ private function generateUniqueIdentifier() { $uniqueIdentifier = bin2hex(openssl_random_pseudo_bytes(40)); //       . $this->validate([ 'string not_empty' => $uniqueIdentifier, ]); return $uniqueIdentifier; } }
      
      







別の例:画面にメッセージを表示するPenクラス(一定量のインクで初期化される単純なインクペン)。



ペンクラス
 <?php namespace models; use traits; /** * Class Pen *   . */ class Pen { use \traits\Validator; /** *    . * * @var double */ private $remainingAmountOfInk; /** * @construct */ public function __construct() { $this->remainingAmountOfInk = 100; } /** *    . * * @param string $message . * * @return void * * @throws ValidatorException      . */ public function drawMessage($message) { $this->validate([ 'string' => $message, ]); if (0 > $this->remainingAmountOfInk) { echo 'Ink ended'; //   } echo 'Pen writes message: ' . $message . '<br>' . PHP_EOL; $this->remainingAmountOfInk -= 1; } /** *    . * * @return integer */ public function getRemainingAmountOfInk() { $this->validate([ 'double' => $this->remainingAmountOfInk, ]); return $this->remainingAmountOfInk; } }
      
      







さて、テーブルにペンを書いてみましょう:「Hello World」!



 // autoload.php -  . /** * Class Autoloader *      . */ class AutoLoader { /** *    . * * @param string $className   . */ public static function loadClasses($className) { $dir = dirname(__FILE__); $sep = DIRECTORY_SEPARATOR; require_once("{$dir}{$sep}{$className}.php"); } } //  . spl_autoload_register([ 'AutoLoader', 'loadClasses' ]); // ------------------------------------ // index.php include_once("autoload.php"); use models as m; $pen = new m\Pen(); $pen->drawMessage('hi habrahabr'); // Pen writes message: hi habrahabr $message = [ 'message' => 'hi im message inside array', ]; try { $pen->drawMessage($message); //    ValidatorException } catch (\Exception $e) { echo 'exception was throwed during validation of message <br>' . PHP_EOL; }
      
      







結論:

 Pen writes message: hi habrahabr exception was throwed during validation of message
      
      







おわりに



ここでは、このような単純な特性の助けを借りて、異なるクラスのメソッドをコピーすることなく、象のsi-sharpeに関数の入力/出力パラメーターを検証させることができます。



上記の例のvalidate()メソッドを特定の検証パラメーター(具体的には、doubleまたはstring変数の最小/最大値、パラメーター検証のカスタムコールバック、例外がスローされたときにメッセージを表示するなどに具体的にねじ込みませんでした。



なぜなら、この記事の主な目的は、言語の静的化を可能にするテクノロジーが、古いバージョンのPHPでも簡単に実装されるという事実について話すことだったからです。



All Articles