Alberto Guilherme
Alberto Guilherme

Reputation: 348

Is there a logic problem or a bug in PhpStorm?

Is there a logic error/case that I'm not seeing?

I'm using PhpStorm 2017.1 with PHP language level 7.1 and CLI Interpreter PHP 7.1.8

I tried the following cases:

  1. User with carrinho(DB) and with and without carrinho_id on request
  2. User without carrinho(DB) and with and without carrinho_id on request
  3. carrinho_id on request with no user
  4. carrinho_id with wrong user(not logged)
$user = Auth::guard('api')->user();
if(!(isset($user) && ($carrinho = $user->getCarrinho()) != null)){
    if(!isset($data['carrinho_id']) || (($carrinho = Carrinho::find($data['carrinho_id'])) == null) || ($carrinho->usuario_id != null))
        $carrinho = Carrinho::salvar([]);
    if(isset($user))
        $carrinho->setUsuario($user->id);
}

if($carrinho->addProduto($data)) //"$carrinho" Variable might have not been defined
    return response()->json([
        'carrinho_id' => $carrinho->id
    ]);
return response()->json(['msg' => "O produto já está no seu carrinho"],422);

Two possible cases

$user exist

if(!(true && ($carrinho = $user->getCarrinho()) != null))

2 two possible path

1 - Has $carrinho

if(!(true && true)) -> !(true && true) -> !(true) -> false -> skip if and load the $carrinho from the user

2 - doesn't have $carrinho

if(!(true && false)) -> !(true && false) -> !(false) -> true -> Go inside the if and define the $carrinho

The code inside the first if will allways have a $carrinho

The problem lies on the first if. How do i know that? Because if I do this, the warning goes off.

if(!(isset($user) && ($carrinho = $user->getCarrinho()) != null)){
    if(!isset($data['carrinho_id']) || (($carrinho = Carrinho::find($data['carrinho_id'])) == null) || ($carrinho->usuario_id != null))
        $carrinho = Carrinho::salvar([]);
    if(isset($user))
        $carrinho->setUsuario($user->id);
}else{
    $carrinho = Carrinho::salvar([]);
}

Upvotes: 1

Views: 99

Answers (3)

Alberto Guilherme
Alberto Guilherme

Reputation: 348

After some questioning and some reseach, I came to the conclusion that hard code does not help anyone, in tearms of team work and project growth. I decided to implement the code in the following way:

$user = Auth::guard('api')->user();
$hasUser = isset($user);
if($hasUser)
    $carrinho = $user->getCarrinho();
if(!isset($carrinho)){
    if(isset($data['carrinho_id']))
        $carrinho = Carrinho::find($data['carrinho_id']);
    if(!isset($carrinho) || $carrinho->usuario_id != null)
        $carrinho = Carrinho::salvar([]);
    if($hasUser)
        $carrinho->setUsuario($user->id);
}
if($carrinho->addProduto($data))
    return response()->json([
        'carrinho_id' => $carrinho->id
    ]);
return response()->json(['msg' => "O produto já está no seu carrinho"],422);

I will not accept this anwser because I'm still not conviced that the code had errors because no one could find any. But I will leave this here to show that hard code does no help.

Upvotes: 0

IMSoP
IMSoP

Reputation: 97678

This code is extremely hard to read, because you've combined side effects (assignments) with complex conditions involving multiple boolean operators. Let's try to write it out as a set of discrete operations:

// First condition to be evaluated is isset($user)
$haveUser = isset($user);
// If that condition is false, the && will lazily skip the next part
if ( $haveUser ) {
    // Now we conditionally assign $carrinho ...
    $carrinho = $user->getCarrinho();
    // ... and test its value
    $haveCarrinho = ($carrinho != null);
}
// Having done all that, we combine the two conditions
$haveBoth = $haveUser && $haveCarrinho;
// Now we invert that condition for our if statement
if ( ! $haveBoth ) {
    // We know here that $carrinho has either never been set (because $haveUser was false) ...
    //   ... or it was null (setting $haveCarrinho to false)

    // Line 3 - another if statement to unpack
    $haveIdInData = isset($data['carrinho_id']);
    // If that condition is false, the || will shortcut
    if ( ! $haveIdInData ) {
        $carrinho = Carrinho::salvar([]);
    }
    // If we don't short-cut, the rest of line 3 runs
    else {
        // Try finding it by the ID in $data
        $carrinho = Carrinho::find($data['carrinho_id']);
        // If it's null, we short-cut at the next ||
        if ($carrinho == null) {
            $carrinho = Carrinho::salvar([]);
        }
        else {
            // Else we make the next check
            if ($carrinho->usuario_id != null) {
                $carrinho = Carrinho::salvar([]);
            }
        }
    }

    // On to line 4! Reusing our condition from above, since the state of $user won't have changed
    if ( $haveUser ) {
        // This will give a horrible error if $carrinho is null
        $carrinho->setUsuario($user->id);
    }
}

// We've reached line 5, and expect $carrinho to be set, but we have no guarantee of that at all!

That's a lot of logic for 4 lines of code!

Tidying up a little bit, without making it as cryptic as the original, I think this is equivalent:

$carrinho = null;
if ( isset($user) ) {
    // Now we conditionally assign $carrinho ...
    $carrinho = $user->getCarrinho();
}
if ( $carrinho == null ) {
    if ( isset($data['carrinho_id']) ) {
        $carrinho = Carrinho::find($data['carrinho_id']);

        if ($carrinho == null || $carrinho->usuario_id != null) {
            $carrinho = Carrinho::salvar([]);
        }
    }
    if(isset($user)) {
        $carrinho->setUsuario($user->id);
    }
}

Now we can see what was probably intended: the Carrinho::salvar line should be the fallback for any other undefined state, rather than nested inside other conditions.

With a bit of effort, we can eliminate nested conditions altogether, giving something much more readable like this:

// Initialise variables to a known state
$carrinho = null;
$loadedFromUser = false;
// Try on user object
if ( isset($user) ) {
    $carrinho = $user->getCarrinho();
    $loadedFromUser = ($carrinho != null);
}
// Not found? Try looking up by input data
if ( $carrinho == null && isset($data['carrinho_id']) ) {
    $carrinho = Carrinho::find($data['carrinho_id']);
}
// Discard if it already has a user ID, but wasn't loaded from user
if (!$loadedFromUser && $carrinho != null && $carrinho->usuario_id != null) {
    $carrinho = null;
}
// Still not found? Create an empty one
if ($carrinho == null) {
    $carrinho = Carrinho::salvar([]);
}

// Now we know we have an object some way or another, and can assign a user ID to it
if(isset($user) && !$loadedFromUser) {
    $carrinho->setUsuario($user->id);
}

Some of these conditions might not be quite what was intended, but by splitting them out, we can now follow the logic much more easily and make changes as appropriate.

Upvotes: 1

Maksym Fedorov
Maksym Fedorov

Reputation: 6456

Your code may have a problem during execution and PHPStorm says about it. You won't have $carrinho object for some cases, for example, if $user variable exists

if(!(isset($user) && ($carrinho = $user->getCarrinho()) != null)){
    if(!isset($data['carrinho_id']) || (($carrinho = Carrinho::find($data['carrinho_id'])) == null) || ($carrinho->usuario_id != null))
        $carrinho = Carrinho::salvar([]);
    if(isset($user))
        $carrinho->setUsuario($user->id);
}

and code $carrinho->addProduto($data) will fail.

You need to fix it. For example, you сan move your code into conditions block

if(!(isset($user) && ($carrinho = $user->getCarrinho()) != null)){
    if(!isset($data['carrinho_id']) || (($carrinho = Carrinho::find($data['carrinho_id'])) == null) || ($carrinho->usuario_id != null)) {
        $carrinho = Carrinho::salvar([]);
    }
    if(isset($user)) {
        carrinho->setUsuario($user->id);
    }
    if($carrinho->addProduto($data)) {
        return response()->json([
            'carrinho_id' => $carrinho->id
        ]);
   }

}

return response()->json(['msg' => "O produto já está no seu carrinho"],422);

Upvotes: 1

Related Questions