如何创建一个可以转换为布尔值(真或假)的PHP类

14

我正在创建一个集合类,希望它可以替代我目前使用的数组。

如何创建一个类,使其可以被转换为布尔值,从而使该类可以是真或假的?

一个简单的测试表明,一个空类的对象是真的:

class boolClass {}
$obj = new boolClass();
var_dump( (bool)$obj);
//prints 
//bool(true)

但是我需要确定我的类是真还是假。有没有办法告诉PHP引擎如何将我的类转换为布尔值?就像我可以使用__toString()一样?

背景:

假设我编写了以下类(仅为示例):

class MyCollection implements ArrayAccess, Iterator {
    //...
}

我目前经常使用以下这些模式:

$var = array();

if (empty($var)) {
   //array is empty, (or there is no array at all)
   // I do something here
}

我希望它看起来像这样:

$var = new MyCollection(array());

并保留其余部分不变。但是包含 MyCollection 的 $var 总是为真,因此我需要将所有条件改为:

if ($var->isEmpty()) {
    //...
}

但这是不可接受的,因为我的代码库有很多兆字节。

这里有什么解决方案吗?


我认为你需要在类内部编写一个方法,以便在对象当前状态为真或假时返回该状态。 - Khurram Ijaz
这里有更多的答案:https://dev59.com/G2035IYBdhLWcg3wMM8G#5572929,例如一个支持此类转换的PHP扩展https://github.com/p1ncet/obcast。 - key_
7个回答

27

经过长时间的烦恼、失望和破解,我相信我已经找到了一个解决方案。这个解决方案不需要任何扩展;它可以通过一小段PHP模板来实现。但是,在您自己实施此解决方案之前,请注意,这实际上是一个非常大的黑客攻击。话虽如此,以下是我发现的内容:

感到沮丧后,我花了一些时间查看PHP布尔类型文档。尽管用户创建的类被简单地拒绝为布尔类型强制转换,但有一个类却奇怪地被赋予了这种能力。敏锐的读者会注意到,这个类就是内置的SimpleXmlElement。据此推断,任何SimpleXmlElement子类也将继承其独特的布尔类型强制转换功能。虽然从理论上讲这种方法似乎是有效的,但是SimpleXmlElement周围的魔法也削弱了这种方法的实用性。要理解为什么,请考虑以下示例:

class Truthy extends SimpleXmlElement { }

Truthy是SimpleXmlElement的一个子类,因此我们应该能够测试它是否继承了其特殊的布尔转换属性:

Truthy是SimpleXmlElement的一个子类,因此我们应该能够测试它是否继承了其特殊的布尔转换属性:

$true = new Truthy('<true>1</true>'); // XML with content eval's to TRUE
if ($true) echo 'boolean casting is happening!'; 
$false = new Truthy('<false></false>'); // empty XML eval's to FALSE
if (!$false) echo 'this is totally useful!';

事实上,SimpleXmlElement所提供的布尔强制属性也被Truthy继承。但是,这种语法很笨拙,并且与原生使用SimpleXmlElement相比,很可能无法获得太多效用。这种情况就是问题开始出现的地方:

$false = new Truthy('<false></false>'); // empty XML eval's to FALSE
$false->reason = 'because I said so'; // some extra info to explain why it's false
if (!$false) echo 'why is this not false anymore?!?';
else echo 'because SimpleXMLElements are magical!';

正如你所看到的,试图在我们的子类上设置属性会立即破坏我们从继承的布尔强制转换中获得的效用。不幸的是,SimpleXmlElement 还有另一个神奇的功能,它打破了我们的约定。显然,当您设置 SimpleXmlElement 的属性时,它会修改XML!亲自看看:

$xml = new SimpleXmlElement('<element></element>');
$xml->test = 'content';
echo $xml->asXML(); // <element><test>content</test></element>

那么我们从子类化SimpleXmlElement中获得的任何效用都没有了!经过长时间的试验,幸运的是,我找到了一种方法来将信息保存到这个子类中,而不会破坏布尔型转换的魔力:注释!

$false = new Truthy('<!-- hello world! --><false></false>');
if (!$false) echo 'Great Scott! It worked!';

进展顺利!我们成功地将有用的信息传递到这个类中,而不会破坏布尔强制转换!好的,现在我们只需要清理一下,这是我的最终实现:

class Truthy extends SimpleXMLElement {

public function data() {

    preg_match("#<!\-\-(.+?)\-\->#", $this->asXML(), $matches);

    if (!$matches) return null;

    return unserialize(html_entity_decode($matches[1]));


}

public static function create($boolean, Serializable $data = null) {
    $xml  = '<!--' . htmlentities(serialize($data)) . "-->";
    $xml .= $boolean ? '<truthy>1</truthy>' : '<truthy/>';
    return new Truthy($xml);
}

}

为了消除一些笨拙,我添加了一个公共的静态工厂方法。现在我们可以创建一个Truthy对象,而不用担心实现细节。工厂允许调用者定义任意的数据集合以及布尔转换。稍后可以调用数据方法来检索只读副本:

$false = Truthy::create(false, array('reason' => 'because I said so!'));
if (!$false) {
   $data = $false->data();
   echo 'The reason this was false was ' . $data['reason'];
}

到此为止!这是一种非常草率(但可用)的方法,可以在用户定义的类中进行布尔转换。如果您在生产代码中使用它并且出现问题,请不要起诉我。


1
呵呵,真不可思议!:) 不幸的是,在最后一轮的优化中:如果你已经添加了Truthy::create,那么你也可以直接在那里返回null(使用try/catch)如果构造失败。而且,由于这是我们检查对象作为布尔值(用于(不)成功初始化)的绝大多数情况,很难证明这样做是有意义的。不过,这仍然是一个非常有趣的技巧,干杯! - Sz.
我们在代码中使用了这个hack几年,但当需要使用此对象进行任何序列化操作时,它就无法正常工作。SimpleXmlElementserialize()无法很好地协同工作。因此我们现在正在移除这个hack。 - dwenaus

9

6
您可以查看PHP operator扩展,您可以使用它来重载许多运算符,包括==和===。使用此扩展,您应该可以理论上编写一个类似布尔值的可比较类,如下所示:
if($object == true)

哇,从没想过在 PHP 中也能实现。我会看一下的。 - SWilk

2

不可能。

在PHP中,当对象被转换为 bool 时,总是会产生true。没有办法改变这个结果。


1

嗯,有一个__invoke方法似乎与您的意图接近:

class boolClass {
    public function __invoke() {
        return true;
    }
}
$obj = new boolClass();
var_dump( (bool)$obj());
//prints 
//bool(true)

0

如果您不想实现自己的方法,您可以通过“__toString”进行黑客攻击,就像这样:

class foo
{
    public function __toString()
    {
        return strval(false);
    }
}

$foo = new foo();

if (strval($foo) == false) // non-strict comparison
{
   echo '$foo is falsy';
}

echo (bool) strval($foo); // false

我仍需更改所使用的条件。如果必须这样做,我宁愿使用一种检查集合是否为空的方法。请参阅我在@Adam Bergmark帖子下的评论。 - SWilk
@SWilk:是的,但我认为即使使用PECL操作员包,也不能在不实现自定义方法的情况下做到这一点。 =\ - Alix Axel

0

如果你的代码大小为“许多兆字节”,问题可能不是你的命名方案太冗长。相反,我会寻找重复并尝试抽象代码。

另一方面,为什么代码的大小是一个大问题?尝试最小化每次运行php时包含的代码。未使用的杂散代码没有任何区别。如果你必须包含很多代码,请考虑使用缓存软件,例如MMCache。

回答你最初的问题,据我所知,没有办法将类型强制转换添加到PHP类中。实例始终会计算为true。


我可能表达不够准确。我所工作的应用程序是一个几年前开始的较小的CRM系统,逐渐发展成为一个完整的ERP系统。有一百多个不同的模块来实现各自的业务目标。目前有许多共同的结构,它们以对象数组的形式存储,而我想将它们转换为集合。由于我想使用新技术,我没有资源来重构所有模块以使用新的Collection类。我需要一个向后兼容的解决方案。 - SWilk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接