PHP logical operators combine two true-or-false answers into one. PHP gives you two sets of them: the symbols &&, || and !, and the words and, or and xor. Most tutorials list both sets and move on. That gap is where the bugs live, because and is not a synonym for &&.
The two families of PHP logical operators
Here is every one of them, side by side.
| Symbol | Word | True when |
&& | and | Both sides are true |
|| | or | At least one side is true |
| โ | xor | Exactly one side is true |
! | โ | Flips the answer |
Two gaps in that table are worth noting now. There is no symbol for xor, and there is no word for !. PHP has no not keyword.
As a plain test, the pairs really do behave the same.
<?php
$loggedIn = true;
$isAdmin = false;
var_dump($loggedIn && $isAdmin);
var_dump($loggedIn and $isAdmin);
var_dump($loggedIn || $isAdmin);
var_dump($loggedIn or $isAdmin);
?>
Output

Same values, same answers. So far the words look like a friendlier way to write the symbols. That impression is exactly the problem.
The one difference: precedence
Every operator in PHP has a precedence โ a rank that decides what runs first. The symbols and the words sit in very different places on that list.
&& and || rank above the assignment operator =. The words and, or and xor rank below it. In fact they are the three lowest-ranked operators in the whole language.
That single difference changes what your code does.
<?php
$a = true && false;
$b = true and false;
var_dump($a);
var_dump($b);
?>
Output

Read that again. Same values, same intent, opposite results. $a is false, which is what you expected. $b is true.
What PHP actually runs
PHP is not being clever. It is applying the ranking, and the ranking puts = first.
<?php
// What you wrote
$b = true and false;
// What PHP actually runs
($b = true) and false;
var_dump($b);
?>
Output

The assignment happens first. $b takes the value true. Only then does PHP evaluate and false โ and it throws that answer away, because nothing is catching it.
Your variable never sees the and at all.
How to fix it
Two ways. Use the symbols, or add brackets.
<?php
$b = (true and false);
$c = true && false;
var_dump($b);
var_dump($c);
?>
Output

Brackets beat everything, so the first line now works. The second line never had the problem.
Brackets fix the symptom. Using && and || removes the cause, which is why almost every PHP style guide tells you to prefer them.
When or is genuinely the right choice
The low precedence is not a design mistake. There is one idiom built entirely on it.
<?php
$handle = fopen("data.txt", "r") or die("Could not open the file.");
echo "File opened successfully.";
fclose($handle);
?>
Output

Because = ranks higher than or, $handle receives whatever fopen() returned before the or is considered. If that value is truthy, die() never runs.
Swap in || and the line breaks. || ranks above =, so PHP would compare fopen(...) with die(...) first and hand $handle a plain true instead of the file.
You will meet this pattern in older codebases. Modern PHP tends to use exceptions instead, but it is worth recognising.
The xor operator
xor is true when exactly one side is true โ never both, never neither.
<?php
$hasCoupon = true;
$isMember = true;
var_dump($hasCoupon xor $isMember);
$isMember = false;
var_dump($hasCoupon xor $isMember);
?>
Output

Both true gives false. One true gives true. Think of it as "one or the other, but not both" โ a discount that cannot be stacked, a radio button, a toggle.
Do not reach for ^ here. That is the bitwise XOR and it works on numbers, not on true and false.
Short-circuiting
All PHP logical operators are lazy, and that is useful. With &&, if the left side is false, PHP never looks at the right side. The answer cannot change, so it does not bother. With ||, a true left side ends it the same way.
This lets you guard a risky check behind a safe one. Writing if (isset($user) && $user->isActive()) is safe, because the method call never runs when $user is missing. Swap the two sides around and you get an error.
The one exception is xor. It has to know both sides before it can answer, so it always evaluates both.
Which ones should you use?
Use &&, || and ! everywhere. They rank above =, so they do what they look like they do.
Keep and and or for the one case where you want the low precedence on purpose, like the or die() line above. If you ever use them inside an assignment, wrap the expression in brackets and make your intent obvious to the next person reading it.
Common questions about PHP logical operators
The questions that come up most once people notice the two sets exist.
What is the difference between and and && in PHP?
Only precedence. As tests they give identical answers, but && ranks above = while and ranks below it. So $x = true and false; stores true, because the assignment runs first. $x = true && false; stores false.
Is or the same as || in PHP?
Logically yes, positionally no. || ranks above = and or ranks below it, which is why $result = fopen($f, "r") or die(); works and the same line with || does not.
Does PHP have a not keyword?
No. ! is the only negation operator PHP has. There is a word form for and, or and xor, but nothing for !.
What does xor do in PHP?
It returns true when exactly one of its two sides is true. Both true or both false give false. It has no symbol form โ ^ is the bitwise XOR, which works on numbers instead.
Why do PHP logical operators sometimes skip code?
That is short-circuiting, and it is deliberate. Once the answer is settled, PHP stops. A false left side ends an &&; a true left side ends an ||. Only xor always checks both.
What is next
You now know why two operators that read the same can behave differently. The rule underneath it is precedence, and it governs far more than logic.
Read PHP operator precedence: what runs first and why for the full picture. For the whole set of operators from the beginning, go back to PHP operators: arithmetic, comparison and logical.
Loading comments...