简体   繁体   中英

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([]);
}

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM