【问题标题】:PHP: Is type-checking return values a good practice to compensate PHP's lack of generics?PHP:类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗?
【发布时间】:2014-10-23 07:43:48
【问题描述】:

注意:为了防止被否决,因为良好做法可能是基于意见的 - 您也可以将问题改写为:类型检查返回的缺点是什么值来弥补 PHP 缺乏泛型?(我没有使用它,因为它暗示存在 缺点)。

问题

来自 Java/C# 世界,PHP 的松散类型处理总是有些烦人。引入输入参数的类型提示时它变得更好,但我仍然缺少generics 和返回值的类型提示。

我发现自己偶尔会通过显式检查代码中的类型来解决这个问题——这感觉有点不对劲,因为语言本身可以为我处理它——我想把这些问题推给社区:

  • 类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗?
  • 有更好/更标准的方法吗?
  • 目前是否正在讨论泛型以供将来在 PHP 中实现?

示例

要更好地了解为什么会出现问题,请考虑以下示例:

假设我们正在构建一个框架来将输入数据转换为其他一些输出数据。示例:

通过 xpath 表达式选择 DomDocument 的标题,将表示 XML 文档的字符串转换为 DomDocument 到另一个字符串。

(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)
    }

在 C# 中,我会使用 generics 来解决这个问题:

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;
  }
  // ...
}

【问题讨论】:

  • 好主意与否,实际上没有人这样做。
  • 如果您使用类型提示或简单地对所有函数和对象的 input 参数进行类型验证,那么无论如何,这个问题几乎不存在。错误可能会进一步传播,因为它们是在下一个函数调用而不是在函数返回时进行类型检查的,但实际上这几乎不是问题。如果你绝对坚持这样的检查,我会添加一个简单的助手:\My\InvalidArgumentException::assertIs($returnValue, 'string')
  • @deceze 检查输入参数引入了一个全新的问题,因为您需要将输入传播给前任——他们可能期望不同类型的输入参数。在 Java/C# 中,我将解决使用方法重载的问题——这(再次)在 PHP 中不可用......
  • 没错,这些东西并没有融入语言本身;如果您想要这些功能,则必须在用户代码中复制它们。返回类型提示必须通过显式类型检查、通过switch 语句或类似语句重载的函数来完成。这些功能可以使代码更简洁,但它们的不存在并不会从根本上影响您编写同类功能的能力。

标签: php generics typechecking type-hinting


【解决方案1】:

我会按照你的方法:测试 $this-&gt;predecessor-&gt;transform($data) 的返回值数据类型,如果不是预期的,则抛出异常。

不知道你是否对Hack programming language by Facebook感兴趣:

Hack 是一种无缝互操作的 HHVM 编程语言 用 PHP。 Hack 将 PHP 的快速开发周期与 静态类型提供的纪律,同时增加了许多功能 常见于其他现代编程语言中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-03
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 2015-08-10
    • 1970-01-01
    • 2012-06-22
    • 2017-03-16
    相关资源
    最近更新 更多