Skip to content

NotOptimalIfConditions ​

warning on by default

Group: Control flow · PhpStorm name: NotOptimalIfConditionsInspection

Looks at the conditions of if/elseif statements and flags four kinds of problems: a cheap operand evaluated after an expensive one in an &&/|| chain (short-circuiting would save work if swapped), and/or keyword operators (low precedence, error-prone), an equality comparison on a value that the same && chain also tests with instanceof (likely a logic bug), and an instanceof check made redundant by another instanceof on the same subject against a related type.

Example ​

php
<?php
// D1 ordering
if (strlen($name) > 3 && $enabled) {}
if (\json_encode($id) || empty($cache)) {}
if (isset($rows[md5($k)]) && $limit) {}
if (!(trim($v) && ($v))) {}
if ($ok) {} elseif (json_decode($raw) || is_string($w)) {}
if (preg_match('/^v\d/', $tag) && $strict) {}
if ($enabled && strlen($name) > 3) {}
if (is_int($n) && $n) {}
if (($total = $cart->sum()) && $total > 10) {}
if (count($items) > 0 && $items[0]) {}
if (($head = array_pop($stack)) && $stack) {}
if (!isset($map[lower($key)]) && !array_key_exists($key, $map)) {}
// S4: side effects are never reordered
if (fwrite($log, $line) !== false && $verbose) {}
if (rename($tmp, $target) || $force) {}
if (session_start() && $user) {}
if (preg_match('/^v(\d+)/', $tag, $parts) && $strict) {}
if (recompute($totals) > 0 && $dirty) {}       // user function: impure
if (strlen(file_get_contents($path)) && $ok) {}
if ($repo->find($id) || empty($cache)) {}       // method call: impure
if (isset($rows[md5($k)]) && $rows) {}          // S3: $rows itself is guarded
if (fetch($a) and $b) {} // one operand for D1

// property fetches
final class Folder {
    public bool $open = false;
    public array $files = [];
    public bool $isEmpty { get => $this->files === []; }
    public function __construct(public ?string $label = null) {}
}
final class Lazy {
    public function __get(string $n) { return load($n); }
}
function scan(Folder $f, Lazy $l, $any, string $p) {
    if (strlen($p) > 3 && $f->open) {}
    if (trim($p) || $f->label) {}
    if (strlen($p) > 3 && !$f->isEmpty) {}      // get hook: computed, impure
    if (is_dir($p) && $l->cached) {}            // __get(): computed, impure
    if (strlen($p) > 3 && $any->flag) {}        // untyped receiver: unknown
    if ($f->isEmpty || $f->open) {}             // computed neighbour: impure
}

// D2 keyword operators
if ($p AND $q) {}
if ($p || ($q or $r)) {}
if ($p xor $q) {}

// D3 equality next to instanceof
if ($node != null && $node instanceof Leaf && $node->ok) {}
if ($node instanceof Leaf || $node === null) {}

// D4 redundant instanceof
interface Shape {}
class Circle implements Shape {}
class Ring extends Circle {}
if ($s instanceof Shape || $s instanceof Ring) {}
if ($s instanceof Circle && $s instanceof Shape) {}
if ($s instanceof Circle || $t instanceof Shape) {}

Reported:

  • line 3: Cheaper check placed after a costlier one; evaluate it first.
  • line 4: Cheaper check placed after a costlier one; evaluate it first.
  • line 5: Cheaper check placed after a costlier one; evaluate it first.
  • line 6: Cheaper check placed after a costlier one; evaluate it first.
  • line 7: Cheaper check placed after a costlier one; evaluate it first.
  • line 8: Cheaper check placed after a costlier one; evaluate it first.
  • line 24: Use '&&' instead of 'and'.
  • line 37: Cheaper check placed after a costlier one; evaluate it first.
  • line 38: Cheaper check placed after a costlier one; evaluate it first.
  • line 46: Use '&&' instead of 'and'.
  • line 47: Use '||' instead of 'or'.
  • line 51: Equality check on a value also tested with instanceof; verify the logic.
  • line 58: Redundant instanceof: another check on the same value already covers this type.
  • line 59: Redundant instanceof: another check on the same value already covers this type.

Options ​

OptionTypeDefaultEffect
REPORT_LITERAL_OPERATORSbooltrueenables D2
REPORT_INSTANCE_OF_FLAWSbooltrueenables D3 and D4
SUGGEST_OPTIMIZING_CONDITIONSbooltrueenables D1

Upstream tests set one option to true without disabling the others, so each fixture is effectively evaluated with all checks on.

Configure ​

In custos.json:

json
{
  "rules": {
    "NotOptimalIfConditions": {
      "enabled": false,
      "options": {
        "REPORT_LITERAL_OPERATORS": true,
        "REPORT_INSTANCE_OF_FLAWS": true,
        "SUGGEST_OPTIMIZING_CONDITIONS": true
      }
    }
  }
}

Suppress ​

Before the statement or declaration (or the first statement of the file), either of:

php
// @custos-ignore NotOptimalIfConditions

/**
 * @noinspection NotOptimalIfConditionsInspection
 */

Released under the MIT License. Rule catalogue modelled on Php Inspections (EA Extended); independent clean-room implementation.