【发布时间】: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