PHP: Является ли проверка типов возвращаемых значений хорошей практикой, чтобы компенсировать отсутствие дженериков в PHP?

Примечание. Чтобы предотвратить отрицательные отзывы, поскольку рекомендуемая практика может быть основана на мнении, вы также можете перефразировать вопрос следующим образом: Каковы недостатки возвращаемых значений проверки типов для компенсации Отсутствие дженериков в PHP? (я не использовал это, так как это подразумевает наличие существующих недостатков).

Вопрос

Пришедшая из мира Java/C# свободная обработка типов в PHP всегда несколько раздражала. Стало лучше, когда были введены подсказки типов для входных параметров, но мне по-прежнему не хватает универсальных шаблонов и -подсказка для возвращаемых значений.

Я обнаружил, что иногда обхожу это, явно проверяя типы в моем коде, что кажется несколько неправильным, поскольку сам язык может справиться с этим за меня, и я хотел бы задать эти вопросы сообществу:

  • Являются ли возвращаемые значения проверки типов хорошей практикой, чтобы компенсировать отсутствие обобщений в PHP?
  • Есть ли лучший/более стандартный способ сделать это?
  • Обсуждаются ли в настоящее время дженерики для будущей реализации в PHP?

Пример

Чтобы лучше понять, почему это проблематично, рассмотрим следующий пример:

Предположим, мы создаем структуру для преобразования входных данных в некоторые другие выходные данные. Примеры:

Преобразуйте строку, представляющую XML-документ в DomDocument, в другую строку, выбрав заголовок указанного DomDocument с помощью выражения xpath.

(string) $xml =[TransformToDomDocument]=> (DomDocument) $doc =[TransformToString]=> (string) $title

Теперь давайте предположим, что входные данные представляют собой не строку, содержащую XML, а Json (но в остальном содержат те же данные). Теперь нам нужно преобразовать ввод Json в объект Json и выбрать заголовок с помощью JsonPath. выражение.

(string) $jsonString =[TransformToJson]=> (Json) $jsonObject =[TransformToString]=> (string) $title

(Примечание: второй пример должен прояснить, что вся структура должна быть действительно гибкой.)

Преобразование выполняется с помощью цепочки объектов-адаптеров, которые обрабатывают преобразование из ввода в вывод:

interface AdapterInterface{

  /**
    * Transform some input data into something else.
    * @param mixed $data
    * @return mixed
   */
  public function transform($data);

  /**
    * Set the Adapter that is used to preprocess the $data before calling $this->transform($data)
    * @param AdapterInterface $adapter
   */
  public function setPredecessorAdapter(AdapterInterface $adapter);

}

class XmlToDomDocumentAdapter implements AdapterInterface{

  private $predecessor;

  /**
    * Transform an xml string into a DOMDocument.
    * @param mixed $data
    * @return DomDocument
   */
  public function transform($data){

    if($this->predecessor !== null){
      $data = $this->predecessor->transform($data); 
      // At this point, we just have to "trust" that the predecessor returns a (string)
    }
    $doc = new DomDocument();
    $doc->loadXml($data);
    return $doc;
  }

}

class DomDocumentToStringAdapter implements AdapterInterface{

  private $xpathExpression;

  private $predecessor;

  /**
    * Transform a DomDocument into a string.
    * @param mixed $data
    * @return string
   */
  public function transform($data){

    if($this->predecessor !== null){
      $data = $this->predecessor->transform($data); 
      // At this point, we just have to "trust" that the predecessor returns a (DOMDocument)
    }
    $xpath = new DOMXpath($data);
    $nodes = $xapth->query($this->xpathExpression);
    if($nodes->length > 0){
        throw new UnexpectedValueException("Xpath didn't match");
    }
    $result = $nodes->item(0)->nodeValue;
    return $result;
  }

}

Применение:

$input = "..."
$xmlToDom = new XmlToDomDocumentAdapater();
$domToString = DomDocumentToStringAdapter();
$domToString->setPredecessorAdapter($xmlToDom);
$output = $domToString->transform($input);

Проблемная часть возникает, когда адаптер полагается на своего предшественника, чтобы вернуть правильный ввод.

    if($this->predecessor !== null){
      $data = $this->predecessor->transform($data); 
      // At this point, we just have to "trust" that the predecessor returns a (DOMDocument)
    }

В С# я бы решил эту проблему, используя дженерики :

interface AdapterInterface{

  /**
    * Tranform some input data into something else.
    * @param mixed $data
    * @return T
   */
  public function T transform<T>(object data);

}

/* using it */
//...

    if(this.predecessor !== null){
      data = this.predecessor.transform<string>(data); 
      // we now know for sure that the data is of type 'string'
    }
//...

Поскольку дженерики не поддерживаются в PHP, я спрашиваю себя, хорошо ли добавлять проверку типа после каждого вызова transform($data), например:

    if($this->predecessor !== null){
      $data = $this->predecessor->transform($data); 
      if(!is_string($data){
        throw new UnexpectedValueException("data is not a string!");
      }
      // we now know for sure that the data is of type 'string'
    }

Мой текущий обходной путь

В настоящее время я использую несколько интерфейсов для определения вывода метода transform следующим образом:

interface ToStringAdapterInterface extends AdapterInterface{

  /**
    * Transform some input data into something else.
    * @param mixed $data
    * @return string <<< define expected output
   */
  public function transform($data);
}

interface ToDomDocumentAdapterInterface extends AdapterInterface{

  /**
    * Transform some input data into something else.
    * @param mixed $data
    * @return DOMDocument<<< define expected output
   */
  public function transform($data);
}

В каждом преобразователе я принимаю только подходящий интерфейс в качестве предшественника:

class DomDocumentToStringAdapter implements ToStringAdapterInterface {

  private $xpathExpression;

  private $predecessor;

  public function __construct(ToDomDocumentAdapterInterface $predecessor){
      $this->predecessor = $predecessor;
  }
  // ...
}

person Hirnhamster    schedule 29.08.2014    source источник
comment
Хорошая идея или нет, практически никто этим не занимается.   -  person Barmar    schedule 29.08.2014
comment
Если вы используете подсказку типа или просто проверяете тип для параметров input для всех ваших функций и объектов, эта проблема в любом случае не существует. Ошибки могут распространяться на один шаг дальше, потому что их тип проверяется при следующем вызове функции, а не во время возврата из функции, но на практике это едва ли проблема. Если вы абсолютно настаиваете на таких проверках, я бы добавил простого помощника: \My\InvalidArgumentException::assertIs($returnValue, 'string')   -  person deceze♦    schedule 29.08.2014
comment
См. также github.com/box/augmented_types.   -  person deceze♦    schedule 29.08.2014
comment
@deceze Проверка входных параметров создает совершенно новую проблему, потому что вам нужно передать ввод предшественнику, который может ожидать другой тип входного параметра. В Java/С# я бы решил использовать перегрузку метода, которая (опять же) недоступна в PHP...   -  person Hirnhamster    schedule 29.08.2014
comment
Правда, эти вещи не встроены в сам язык; если вам нужны эти функции, вам придется реплицировать их в пользовательском коде. Подсказка типа возвращаемого значения должна выполняться посредством явной проверки типа, перегрузки функции с помощью оператора switch или подобного. Эти функции могут сделать код более кратким, но их отсутствие принципиально не влияет на вашу способность писать такие же функции.   -  person deceze♦    schedule 29.08.2014
comment
@deceze Я полностью с тобой согласен. Тем не менее, я считаю весьма полезным узнать мнение других разработчиков и узнать, как они справляются с ситуацией :)   -  person Hirnhamster    schedule 29.08.2014
comment
В этом нет ничего плохого. Мое мнение: решите, насколько велика проблема на самом деле и какие плохие вещи могут случиться, если вы не проверяли возвращаемые типы, а затем решите, нужно вам это или нет. :)   -  person deceze♦    schedule 29.08.2014


Ответы (1)


Я бы последовал вашему подходу: протестируйте тип данных возвращаемого значения $this->predecessor->transform($data) и выдайте исключение, если это не то, что ожидалось.

Я не знаю, может ли вас заинтересовать язык программирования Hack от Facebook:

Hack — это язык программирования для HHVM, который легко взаимодействует с PHP. Hack сочетает быстрый цикл разработки PHP с дисциплиной, обеспечиваемой статической типизацией, добавляя при этом множество функций, обычно встречающихся в других современных языках программирования.

person PauloASilva    schedule 29.08.2014