Skip to content

Mutators ​

Infection supports a set of Mutators which are based on AST and PHP-Parser project.

Read more about how to execute only particular set of mutators using Name column

Run the infection describe command to get information about mutators, right from the command line.

Function Signature ​

NameOriginalMutated
PublicVisibilitypublic function ...protected function ...
ProtectedVisibilityprotected function ...private function ...

To verify that the visibility of a method is necessary. If the visibility of a method can be reduced from public to protected or private, this may be an indication that the publicly accessible part of API of a class is larger than what’s strictly necessary. This mutator will drive the source code towards classes with smaller publicly accessible APIs and thus better encapsulation.

Unwrap Function ​

The Unwrap* mutator family will unwrap function parameters.

NameOriginalMutated
UnwrapArrayChangeKeyCase$a = array_change_key_case(['foo' => 'bar']);$a = ['foo' => 'bar'];
UnwrapArrayChunk$a = array_chunk(['A', 'B', 'C'], 2);$a = ['A', 'B', 'C'];
UnwrapArrayColumn$a = array_column([['foo' => 'bar]], 'foo');$a = [['foo' => 'bar]];
UnwrapArrayCombine$a = array_combine(['A', 'B', 'C'], ['foo', 'bar', 'baz']);$a = ['A', 'B', 'C'];
UnwrapArrayDiff$a = array_diff(['A', 'B', 'C'], ['D']);$a = ['A', 'B', 'C'];
UnwrapArrayDiffAssoc$a = array_diff_assoc(['foo' => 'bar'], ['baz' => 'bar]);$a = ['foo' => 'bar'];
UnwrapArrayDiffKey$a = array_diff_key(['foo' => 'bar'], ['baz' => 'bar]);$a = ['foo' => 'bar'];
UnwrapArrayDiffUassoc$a = array_diff_assoc(['foo' => 'bar'], ['baz' => 'bar], $keyCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayDiffUkey$a = array_diff_ukey(['foo' => 'bar'], ['baz' => 'bar], $keyCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayFilter$a = array_filter(['A', 1, 'C'], 'is_int');$a = ['A', 1, 'C'];
UnwrapArrayFlip$a = array_flip(['A', 'B', 'C']);$a = ['A', 'B', 'C'];
UnwrapArrayIntersect$a = array_intersect(['A', 'B', 'C'], ['D']);$a = ['A', 'B', 'C'];
UnwrapArrayIntersectAssoc$a = array_intersect_assoc(['A', 'B', 'C'], ['D']);$a = ['A', 'B', 'C'];
UnwrapArrayIntersectKey$a = array_intersect_key(['foo' => 'bar'], ['bar' => 'baz']);$a = ['foo' => 'bar'];
UnwrapArrayIntersectUassoc$a = array_intersect_uassoc(['foo' => 'bar'], ['bar' => 'baz'], $keyCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayIntersectUkey$a = array_intersect_ukey(['foo' => 'bar'], ['bar' => 'baz'], $keyCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayKeys$a = array_keys(['foo' => 'bar']);$a = ['foo' => 'bar'];
UnwrapArrayMap$a = array_map('strtolower', ['A', 'B', 'C']);$a = ['A', 'B', 'C'];
UnwrapArrayMerge$a = array_merge(['A', 'B', 'C'], ['D']);$a = ['A', 'B', 'C'];
UnwrapArrayMergeRecursive$a = array_merge_recursive(['A', 'B', 'C'], ['D']);$a = ['A', 'B', 'C'];
UnwrapArrayPad$a = array_pad(['A'], 2, 'B');$a = ['A'];
UnwrapArrayReduce$a = array_reduce(['A', 'B', 'C'], $callback, ['D']);$a = ['D'];
UnwrapArrayReplace$a = array_replace(['A', 'B', 'C'], ['D']);$a = ['A', 'B', 'C'];
UnwrapArrayReplaceRecursive$a = array_replace_recursive(['A', 'B', 'C'], ['D']);$a = ['A', 'B', 'C'];
UnwrapArrayReverse$a = array_reverse(['A', 'B', 'C']);$a = ['A', 'B', 'C'];
UnwrapArraySlice$a = array_slice(['A', 'B', 'C'], 1);$a = ['A', 'B', 'C'];
UnwrapArraySplice$a = array_splice(['A', 'B', 'C'], 1);$a = ['A', 'B', 'C'];
UnwrapArrayUdiff$a = array_udiff(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayUdiffAssoc$a = array_udiff_assoc(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayUdiffUassoc$a = array_udiff_uassoc(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc, $keyCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayUintersect$a = array_uintersect(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayUintersectAssoc$a = array_uintersect_assoc(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayUintersectUassoc$a = array_uintersect_uassoc(['foo' => 'bar'], ['baz' => 'bar], $valueCompareFunc, $keyCompareFunc);$a = ['foo' => 'bar'];
UnwrapArrayUnique$a = array_unique(['foo', 'bar', 'bar']);$a = ['foo', 'bar', 'bar'];
UnwrapArrayValues$a = array_values(['foo' => 'bar']);$a = ['foo' => 'bar'];
UnwrapLcFirst$a = lcfirst('Hello, world!');$a = 'Hello, world!';
UnwrapLtrim$a = ltrim(' Hello, world!');$a = ' Hello, world!';
UnwrapRtrim$a = rtrim('Hello, world! ');$a = 'Hello, world! ';
UnwrapStrIreplace$a = str_ireplace('Afternoon', 'Evening' ,'Good Afternoon!');$a = 'Good Afternoon!';
UnwrapStrRepeat$a = str_repeat('A', 3);$a = 'A';
UnwrapStrReplace$a = str_replace('Afternoon', 'Evening' ,'Good Afternoon!');$a = 'Good Afternoon!';
UnwrapStrRev$a = strrev('Hello!');`$a = ‘Hello!’;
UnwrapStrShuffle$a = str_shuffle('Hello!');$a = 'Hello!';
UnwrapSubstr$a = substr('Hello!', 0, -1);$a = 'Hello!';
UnwrapStrToLower$a = strtolower('Hello!');$a = 'Hello!';
UnwrapStrToUpper$a = strtoupper('Hello, world!');$a = 'Hello, world!';
UnwrapTrim$a = trim(' Hello, world! ');$a = 'Hello, world!';
UnwrapUcFirst$a = ucfirst('hello, world!');$a = 'hello, world!';
UnwrapUcWords$a = ucwords('hello, world!');$a = 'hello, world!';

Binary Arithmetic ​

NameOriginalMutated
Plus+-
Minus-+
Multiplication*/
Division/*
Modulus%*
Exponentiation**/
MulEqual*=/=
PlusEqual+=-=
MinusEqual-=+=
DivEqual/=*=
ModEqual%=*=
PowEqual**=/=
BitwiseAnd&|
BitwiseOr|&
BitwiseXor^&
BitwiseNot~
ShiftRight>><<
ShiftLeft<<>>
AssignmentEqual===
Assignment+==
Assignment-==
Assignment*==
Assignment**==
Assignment/==
Assignment%==
Assignment.==
Assignment&==
Assignment|==
Assignment^==
Assignment<<==
Assignment>>==
Assignment Coalesce??==

Round Family ​

The Round Family mutator will make sure that there’s enough tests to cover the rounding possibilities.

NameOriginalMutated
RoundingFamilyround()floor()
RoundingFamilyround()ceil()
RoundingFamilyceil()floor()
RoundingFamilyceil()round()
RoundingFamilyfloor()round()
RoundingFamilyfloor()ceil()

Boolean ​

NameOriginalMutated
ArrayAllarray_all()true
ArrayAnyarray_any()true
ArrayItem[$a->foo => $b->bar][$a->foo > $b->bar]
TrueValuetruefalse
FalseValuefalsetrue
InstanceOf_$a instanceof Btrue / false
LogicalAnd&&||
LogicalOr||&&
LogicalLowerAndandor
LogicalLowerOrorand
LogicalNot! 
Yield_yield $a => $b;yield $a > $b;
LogicalAndAllSubExprNegation$a = $b && $c;$a = !$b && !$c;
LogicalAndNegation$a = $b && $c;$a = !($b && $c);
LogicalAndSingleSubExprNegation$a = $b && $c;$a = !$b && $c;, $a = $b && !$c;
LogicalOrAllSubExprNegation$a = $b || $c;$a = !$b || !$c;
LogicalOrNegation$a = $b || $c;$a = !($b || $c);
LogicalOrSingleSubExprNegation$a = $b || $c;$a = !$b |&#124, $c; $a = $b || !$c;

TrueValue ​

Default settings:

  • in_array: false: whether to mutate 3rd argument true in function call
  • array_search: false: whether to mutate 3rd argument true in function call

infection.json5:

json
{
    "mutators": {
        "TrueValue": {
            "settings": {
                "in_array": true,
                "array_search": true
            }
        }
     }
}

Conditional Boundaries ​

NameOriginalMutated
GreaterThan>>=
LessThan<<=
GreaterThanOrEqualTo>=>
LessThanOrEqualTo<=<

Equal or Identical Checks ​

NameOriginalMutated
EqualIdentical=====
NotEqualNotIdentical!=!==
NotIdenticalNotEqual!==!=

These mutators are disabled by default, you can use the @equal or @identical profiles to enable the ones you prefer.

Negated Conditionals ​

NameOriginalMutated
Equal==!=
NotEqual!===
Identical===!==
NotIdentical!=====
GreaterThanNegotiation><=
LessThanNegotiation<>=
GreaterThanOrEqualToNegotiation>=<
LessThanOrEqualToNegotiation<=>

Operator ​

NameOriginalMutated
Catch_catch (\Throwable&#124;\DomainException $e)catch (\DomainException $e) catch (\DomainException $e)
Coalesce$foo ?? $bar$bar ?? $foo
Concat$foo . $bar$bar . $foo
NullSafeMethodCall$object?->getObject()$object->getObject()
NullSafePropertyCall$object?->property$object->property
SpreadAssignment$array = [...$collection]$array = $collection
SpreadOneItem[...$collection, 2, 3][[...$collection][0], 2, 3]
SpreadRemoval[...$collection, 2, 3][$collection, 2, 3]
Ternaryisset($b) ? 'B' : 'C'isset($b) ? 'C' : 'B'
ElseIfNegation} elseif ($this->bar()) {} elseif (!$this->bar()) {
IfNegationif ($this->fooBar()) {if (!$this->fooBar()) {

Increments ​

NameOriginalMutated
Increment++--
Decrement--++

Return Values ​

NameOriginalMutated
ArrayOneItemreturn $collection;return count($collection) > 1 ? array_slice($collection, 0, 1, true) : $collection;
TrueValuereturn true;return false;
FalseValuereturn false;return true;
IntegerNegationreturn (Any Integer);return -(Any Integer);
OneZeroFloatreturn 0.0;return 1.0;
OneZeroFloatreturn 1.0;return 0.0;
FloatNegationreturn (Any Float);return -(Any Float);
Thisreturn $this;return null;
FunctionCallreturn function();function(); return null;
NewObjectreturn new Class();new Class(); return null;
YieldValueyield $key => $value;yield $value;

Removal Mutators ​

NameOriginalMutated
ArrayItemRemoval[1, $a, '3'][$a, '3'] depending on configuration
FunctionCallRemovalfoo_bar($a)-
MethodCallRemoval$this->method($var)-
CatchBlockRemovaltry {} catch (\DomainException $e) {} catch (\Throwable $e) {}try {} catch (\DomainException $e) {} try {} catch (\Throwable $e) {}
MatchArmRemovalmatch ($x) { 1 => null, default => throw new \Exception()};match ($x) { default => throw new \Exception()}; match ($x) { 1 => null};
CloneRemovalclone (new stdClass())new stdClass()
ConcatOperandRemoval'foo' . 'bar''bar' and 'foo'
ReturnRemovalreturn $foo;-
SharedCaseRemovalswitch($a) { case 'a': case 'b': break; }switch($a) { case 'b': break; } switch($a) { case 'a': break; }

ArrayItemRemoval ​

Configuration options:

  • remove: first: defines the way the mutator operates. Could be:
    • first - remove only first element from each array
    • last - remove only last element from each array
    • all - remove every element one by one from each array resulting in as many mutations as total number of items in arrays.
  • limit: PHP_INT_MAX: when remove = all specifies maximum number of elements that will be removed form array. Only elements at the beginning will be mutated.

When using all option we advise to set the limit as well

You should remember to exclude files containing large arrays (like configuration)
when using ArrayItemRemoval mutator in all mode

infection.json5:

json
{
    "mutators": {
        "ArrayItemRemoval": {
            "settings": {
                "remove": "all",
                "limit": 15
            }
        }
     }
}

Loop ​

NameOriginalMutated
Break_break;continue;
Continue_continue;break;
Foreach_foreach ($someVar as …);foreach ([] as …);
For_for ($i=0; $i < 10; $i++);for ($i=0; false; $i++);
While_while ($condition);while (false);
DoWhiledo {} while ($condition);do {} while (false);

Sorting ​

NameOriginalMutated
Spaceship$a <=> $b$b <=> $a

Nullify ​

NameOriginalMutated
ArrayFindarray_find()null
ArrayFindKeyarray_find_key()null
ArrayFirstarray_first()null
ArrayLastarray_last()null

Literal Numbers ​

NameOriginalMutated
OneZeroFloat0.01.0
OneZeroFloat1.00.0
DecrementInteger76
IncrementInteger78

Exceptions ​

NameOriginalMutated
Throw_throw new NotFoundException();new NotFoundException();
Finally_try {} catch (\Exception $e) {} finally {}try {} catch (\Exception $e) {}
UnwrapFinallytry { $callback(); } finally { $cleanUpCallbak(); }$callback(); $cleanUpCallbak();

Type Casting ​

NameOriginalMutated
CastArray(array) $value;$value
CastBool(bool) $value;$value
CastFloat(float) $value;$value
CastInt(int) $value;$value
CastObject(object) $value;$value
CastString(string) $value;$value

Regex ​

NameOriginalMutated
PregQuote$a = preg_quote('text');$a = 'text';
PregMatchMatchespreg_match('/pattern/', $value, $matches);(int) $matches = array();
PregMatchRemoveCaretpreg_match('/^test/', $string);preg_match('/test/', $string);
PregMatchRemoveDollarpreg_match('/test$/', $string);preg_match('/test/', $string);
PregMatchRemoveFlagspreg_match('/test$/iu', $string);preg_match('/test$/u', $string);, preg_match('/test$/i', $string);

Extensions ​

BCMath ​

NameOriginalMutated
BCMathbcadd($a, $b, $mod);(string) ($a + $b);
bcdiv($a, $b, $mod);(string) ($a / $b);
bcmod($a, $b, $mod);(string) ($a % $b);
bcmul($a, $b, $mod);(string) ($a * $b);
bcpow($a, $b, $mod);(string) ($a ** $b);
bcsub($a, $b, $mod);(string) ($a - $b);
bcsqrt($a, $mod);(string) \sqrt($a);
bcpowmod($a, $b, $c, $mod);(string) (\pow($a, $b) % $c);
bccomp($a, $b, $mod);$a <=> $b;
  • "bcpowmod": true: You are able to disable any of the supported bcmath functions. All supported functions are enabled by default.

infection.json5:

json
{
    "mutators": {
        "BCMath": {
            "settings": {
                "bcpowmod": false,
                "bccomp": false
            }
        }
     }
}

MBString ​

NameOriginalMutated
MBStringmb_chr($code);chr($code);
mb_ord($character);ord($character);
mb_parse_str('text', $results);parse_str('text', $results);
mb_send_mail($to, $subject, $message, $headers, $parameters);mail($to, $subject, $message, $headers, $parameters);
mb_strcut('text', 0, 123, 'utf-8');substr('text', 0, 123);
mb_stripos('text', 't', 0, 'utf-8');stripos('text', 't', 0);
mb_stristr('text', 't', true, 'utf-8');stristr('text', 't', true);
mb_strlen('text', 'utf-8');strlen('text');
mb_strpos('text', 't', 0, 'utf-8');strpos('text', 't', 0);
mb_strrchr('text', 't', true, 'utf-8');strrchr('text', 't', true);
mb_strripos('text', 't', 0, 'utf-8');strripos('text', 't', 0);
mb_strrpos('text', 't', 0, 'utf-8');strrpos('text', 't', 0);
mb_strstr('text', 't', true, 'utf-8');strstr('text', 't', true);
mb_strtolower('text', 'utf-8');strtolower('text');
mb_strtoupper('text', 'utf-8');strtoupper('text');
mb_substr_count('text', 't', 'utf-8');substr_count('text', 't');
mb_substr('text', 0, 123, 'utf-8');substr('text', 0, 123);
mb_str_split('text', 2, 'utf-8');str_split('text', 2);
mb_convert_case('text', $mode);strtoupper('text');, strtolower('text'); or ucwords('text'); depending on mode
  • "mb_parse_str": true: You are able to disable any of the supported mb string functions. All supported functions are enabled by default.

Some of the functions are not supported due to complexity to convert them to standard string manipulation functions.
Implementing them either does not make sense or creates too many false positive mutations.
Not supported functions are mb_ereg*, mb_split, mb_strrichr, mb_get_info and similar.

infection.json5:

json
{
    "mutators": {
        "MBString": {
            "settings": {
                "mb_send_mail": false,
                "mb_substr_count": false
            }
        }
     }
}