PHP:类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗?
Posted
技术标签:
【中文标题】PHP:类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗?【英文标题】:PHP: Is type-checking return values a good practice to compensate PHP's lack of generics? 【发布时间】: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')
另见github.com/box/augmented_types
@deceze 检查输入参数引入了一个全新的问题,因为您需要将输入传播给前任——他们可能期望不同类型的输入参数。在 Java/C# 中,我将解决使用方法重载的问题——这(再次)在 PHP 中不可用......
没错,这些东西并没有融入语言本身;如果您想要这些功能,则必须在用户代码中复制它们。返回类型提示必须通过显式类型检查、通过switch
语句或类似语句重载的函数来完成。这些功能可以使代码更简洁,但它们的不存在并不会从根本上影响您编写同类功能的能力。
【参考方案1】:
我会按照你的方法:测试 $this->predecessor->transform($data)
的返回值数据类型,如果不是预期的,则抛出异常。
不知道你是否对Hack programming language by Facebook感兴趣:
Hack 是一种无缝互操作的 HHVM 编程语言 用 PHP。 Hack 将 PHP 的快速开发周期与 静态类型提供的纪律,同时增加了许多功能 常见于其他现代编程语言中。
【讨论】:
以上是关于PHP:类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章