Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to create a php class which can be casted to boolean (be truthy or falsy)

Tags:

php

class

boolean

I am creating a collection class and would like it to be drop-in-replacement for arrays, which I use currently.

How to create a class which could be casted to boolean, so the class can be truthy or falsy?

A simple test shows that an object of empty class is truthy:

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

But I need to decide if my class is truthy or falsy. Is there eny way to tell the PHP engine how to cast my class to boolean? Like I could do with __toString()?

Background:

Lets say I write a class like this (it's an example only):

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

I heavily use this patterns currently:

$var = array();

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

I would like that to look like:

$var = new MyCollection(array());

and keep the rest unchanged. But the $var containing MyCollection is always truthy so I would need to all the conditions to:

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

But this is unacceptable, as my codebase have many megabytes.

Any solution here?

like image 651
SWilk Avatar asked May 24 '11 16:05

SWilk


People also ask

Does PHP have truthy?

PHP has truthy booleans, which are values that are not true or false, but when evaluated inside an expression, they can act like they are. The best examples are the integers 0 and 1. If we passed 1 to the if statement, PHP would interpret the number 1 as truthy and would execute the statement inside the body.

How can check boolean value in if condition in PHP?

The is_bool() function checks whether a variable is a boolean or not. This function returns true (1) if the variable is a boolean, otherwise it returns false/nothing.

How initialize boolean in PHP?

php $var=TRUE; echo $var . "\n"; var_dump($var); $var1=false; echo $var1; var_dump($var1); ?>


2 Answers

After much angst, disappointment, and hacking - I believe I have found a solution. The solution doesn't call for any extensions; it can be implemented with a very small amount of PHP boilerplate. However, before implementing this solution yourself, please take note that this is - in fact - a HUGE hack. That being said, here is what I discovered:

Frustrated, I spent some time looking over the PHP documentation for Booleans. While user-created classes are simply denied the ability to be cast as a boolean, one class - oddly enough - was afforded the capability. The astute reader would notice that this class is none other than the built-in SimpleXmlElement. By process of deduction, it seems fair to assume that any subclass of SimpleXmlElement would also inherit its unique boolean-casting capability. While, in theory this approach seems valid, the magic surrounding SimpleXmlElement also takes away from the utility of this approach. To understand why this is, consider this example:

class Truthy extends SimpleXmlElement { }

Truthy is a subclass of SimpleXmlElement, so we should be able to test if its special boolean-casting property was inherited:

$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!';

Indeed, the boolean-casting property afforded to SimpleXmlElement is inherited by Truthy. However, this syntax is clumsy, and it is highly unlikely that one would get much utility out of this class (at least when compared to using SimpleXmlElement natively). This scenario is where the problems start to come up:

$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!';

As you can see, trying to set a property on our subclass immediately breaks the utility we get from the inherited boolean-casting. Unfortunately for us, the SimpleXmlElement has another magical feature that breaks our convention. Apparently, when you set a property of a SimpleXmlElement, it modifies the XML! See for yourself:

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

Well there goes any utility we would get from subclassing SimpleXmlElement! Thankfully, after much hacking, I found a way to save information into this subclass, without breaking the boolean casting magic: comments!

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

Progress! We were able to get useful information into this class without breaking boolean-casting! Ok, now all we need to do is clean it up, here is my final implementation:

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

}

To remove some of the clumsiness, I added a public static factory method. Now we can create a Truthy object without worrying about the implementation details. The factory lets the caller define any arbitrary set of data, as well as a boolean casting. The data method can then be called at a later time to retrieve a read-only copy of this data:

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

There you have it! A totally hacky (but usable) way to do boolean-casting in user-defined classes. Please don't sue me if you use this in production code and it blows up.

like image 176
Jonathan Azoff Avatar answered Oct 07 '22 07:10

Jonathan Azoff


On this page, the magic methods that you can define for your classes are enumerated.

http://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.tostring

You demonstrate that you already know about __toString().

Unfortunately, there is no magic method listed there that does what you are asking. So, I think for now your only option is to define a method and call that method explicitly.

like image 38
Hammerite Avatar answered Oct 07 '22 05:10

Hammerite