Boolean String Normalizer How can the normalize() method below be improved? I've extracted the functionality from a project I'm working on into a simple class for the purposes of this question.

The method takes a string input and compares it to a set of 'truthy' and 'falsey' strings, returning true or false respectively if a case insensitive match is found. The truthy and falsey strings are mutable so cannot be hard coded into the method. 

If the $nullable property is set to true and no match is found, null is returned. Otherwise false is returned.

I feel as though I may be missing a trick by looping through the array and calling strcasecmp twice for each iteration. 

&lt;?php

class BooleanNormalizer
{
    /** @var array */
    public $binaries = [
        'yes'     =&gt; 'no',
        '1'       =&gt; '0',
        'on'      =&gt; 'off',
        'enabled' =&gt; 'disabled',
    ];

    /** @var bool */
    public $nullable = false;

    /**
     * @param string $value
     *
     * @return bool|null
     */
    public function normalize($value)
    {
        foreach ($this-&gt;binaries as $truthy =&gt; $falsey) {
            if (strcasecmp($value, $truthy) === 0) {
                return true;
            }

            if (strcasecmp($value, $falsey) === 0) {
                return false;
            }
        }

        return ($this-&gt;nullable) ? null : false;
    }
}


Usage:

$normalizer = new BooleanNormalizer();

$normalizer-&gt;nullable = true;
$normalizer-&gt;binaries = ['enabled' =&gt; 'disabled'];

$normalizer-&gt;normalize('enabled'); // returns true
$normalizer-&gt;normalize('disabled'); // returns false
$normalizer-&gt;normalize('blah'); // returns null
I guess foremost, I question the mutability of this class. Should one really be able to change the value of $binaries?



I very much question the optional "nullable" thing here.  First of all, name-wise this doesn't seem to make sense as typically when you think of something as "nullable" it means it is a variable, property, field, etc. that is allowed to be set to null.  You are not setting anything here, so the terminology seems weird.  Perhaps it should be called $returnFalseOnMissingValue or something that more directly ties it to the return behavior of the normalize() method.

I would however get rid of it, as it will become a source of fragility to calling code, as in some cases (including default case) your calling code would not be able to distinguish a false "hit" result from a false "miss" result.  Let the calling code deal with converting null to false if it needs to send false up the call stack.  Don't obfuscate the result right here in this class which is designed to own this determination.



Why use strcasecmp()?  This seems an odd choice vs. just casting $value to lowercase for direct comparison.
 i.e.

$value = strtolower($value);
if ($value === $truthy) return true;
if ($value === $falsey) return false;
return null;




I also don't understand the lookup mechanism and comparison mechanisms here.  It seems obscure upon first read to have key/value positioning determine how the string is mapped to true/false. I think I would be more explicit like:

protected $mappings = [
    'yes' =&gt; true,
    'no' =&gt; false,
    '1' =&gt; true,
    '0' =&gt; false,
    'on' =&gt; true,
    'off' =&gt; false;
    'enabled' =&gt; true,
    'disabled' =&gt; false
];


And have your comparison method simply do something like:

public function normalize($value) {
    $value = strtolower($value);
    if(!array_key_exists($value, $this-&gt;mappings) return null;
    return $this-&gt;mappings[$value];
}


If you truly need to keep the concept of true/value pairs for some reason not shown in this code, then perhaps a structure like:

protected $mappings = [
    'yes' =&gt; ['boolean' =&gt; true, 'inverse' =&gt; 'false'],
    'no' =&gt; ['boolean' =&gt; false, 'inverse' =&gt; 'true'],
    ...
];


This prevents you from having to iterate through the $binaries values every time you want to do a lookup like you are currently doing.  The name $binaries also seems odd here as that typically has a much different meaning when talking about writing code.



I would suggest that 'true' and 'false' should be strings in your default mapping of boolean values.



Does this class really need a concrete instantiation?



Consider adding some parameter validation anytime you have public methods.  Ideally this could be a combination of type hinting and/or variable inspection to make sure you are being passed a valid parameter.  What happens now you if your method is passed an integer, array, object, empty string, etc.?