Module 2: PHP Operator Precedence
Learning Objectives
- Master PHP operators
- Understand operator precedence
- Apply operators in practical scenarios
- Write efficient expressions
Introduction to PHP Operator Precedence
Welcome to our session on PHP Operator Precedence! When writing complex expressions in PHP, understanding the order in which operators are evaluated is crucial for creating code that behaves as expected. PHP, like most programming languages, follows specific rules for determining which operations are performed first in an expression containing multiple operators.
Think of operator precedence as a set of traffic rules that govern the flow of operations in your code. Just as some vehicles (like emergency vehicles) have priority on the road, some operators in PHP have higher precedence than others. Today, we'll explore these precedence rules, understand how they affect your code, and learn how to write expressions that are both correct and clear, avoiding common pitfalls.
What is Operator Precedence?
Operator precedence determines the order in which operators are evaluated in an expression. When you write an expression with multiple operators, PHP needs to know which operations to perform first. This is similar to the order of operations in mathematics (remember PEMDAS or BODMAS from school?).
Basic Precedence Example
<?php
// Example 1: Arithmetic precedence
$result1 = 5 + 3 * 2;
echo "5 + 3 * 2 = $result1<br>"; // Output: 11 (not 16)
// Multiplication has higher precedence than addition
// Example 2: Using parentheses to change precedence
$result2 = (5 + 3) * 2;
echo "(5 + 3) * 2 = $result2<br>"; // Output: 16
// Parentheses have the highest precedence
// Example 3: Multiple operators with different precedence
$result3 = 10 - 2 * 3 + 4 / 2;
echo "10 - 2 * 3 + 4 / 2 = $result3<br>"; // Output: 4
// Evaluated as: 10 - (2 * 3) + (4 / 2) = 10 - 6 + 2 = 4
?>
Without a clear understanding of operator precedence, your expressions might be evaluated in an unexpected order, leading to bugs that can be difficult to track down. Consider the difference between $a && $b || $c and $a && ($b || $c) - these can produce very different results!
PHP Operator Precedence Table
PHP operators are listed below in order of precedence, with the highest precedence operators at the top. Operators with the same precedence are grouped together.
| Precedence | Operator(s) | Description | Associativity |
|---|---|---|---|
| 1 | ( ) | Parentheses (grouping) | n/a |
| 2 | ** | Exponentiation | Right |
| 3 | ++ -- | Increment/decrement | n/a |
| 3 | ~ - (int) (float) (string) (array) (object) (bool) ! | Unary operations, type casting | Right |
| 4 | * / % | Multiplication, division, modulus | Left |
| 5 | + - . | Addition, subtraction, string concatenation | Left |
| 6 | << >> | Bitwise shift | Left |
| 7 | < <= > >= <=> | Comparison | n/a |
| 8 | == != === !== <> | Equality | n/a |
| 9 | & | Bitwise AND | Left |
| 10 | ^ | Bitwise XOR | Left |
| 11 | | | Bitwise OR | Left |
| 12 | && | Logical AND | Left |
| 13 | || | Logical OR | Left |
| 14 | ?? | Null coalescing | Right |
| 15 | ? : | Ternary | Left |
| 16 | = += -= *= /= .= %= &= |= ^= <<= >>= **= ??= | Assignment | Right |
| 17 | and | Logical AND | Left |
| 18 | xor | Logical XOR | Left |
| 19 | or | Logical OR | Left |
Note: This precedence table is for PHP 7 and later. Some operators may behave differently in older versions of PHP.
Key Precedence Groups
Let's explore some of the most important precedence groups that you'll encounter frequently in PHP development:
1. Parentheses - The Highest Precedence
Parentheses always have the highest precedence. They allow you to explicitly control the order of operations:
<?php
// Parentheses change the evaluation order
$a = 5;
$b = 3;
$c = 2;
// Without parentheses
$result1 = $a + $b * $c;
echo "Without parentheses: $a + $b * $c = $result1<br>"; // 11
// With parentheses
$result2 = ($a + $b) * $c;
echo "With parentheses: ($a + $b) * $c = $result2<br>"; // 16
// Multiple levels of parentheses
$result3 = ($a + ($b * $c)) / 2;
echo "Nested parentheses: ($a + ($b * $c)) / 2 = $result3<br>"; // 5.5
?>
Best Practice: Use parentheses liberally to make your code more readable and to ensure operations are performed in the intended order, even when the default precedence would give the same result.
2. Arithmetic Operators
Arithmetic operators follow a precedence similar to mathematical conventions:
<?php
// Arithmetic operators precedence
$result1 = 10 + 20 * 3 - 40 / 5;
echo "10 + 20 * 3 - 40 / 5 = $result1<br>"; // 10 + 60 - 8 = 62
// Using parentheses for clarity
$result2 = 10 + (20 * 3) - (40 / 5);
echo "10 + (20 * 3) - (40 / 5) = $result2<br>"; // Same result, but clearer
// Exponentiation has higher precedence than multiplication
$result3 = 2 * 3 ** 2;
echo "2 * 3 ** 2 = $result3<br>"; // 2 * 9 = 18 (not 6^2 = 36)
// Modulus has same precedence as multiplication
$result4 = 10 + 20 % 3 * 2;
echo "10 + 20 % 3 * 2 = $result4<br>"; // 10 + (2 * 2) = 14
?>
Key Points:
- Exponentiation (**) has higher precedence than multiplication, division, and modulus
- Multiplication (*), division (/), and modulus (%) have equal precedence
- Addition (+) and subtraction (-) have equal precedence, lower than multiplication/division
3. String Concatenation
The string concatenation operator (.) has the same precedence as addition and subtraction:
<?php
// String concatenation precedence
$name = "John";
$greeting = "Hello, " . $name . "! Today is " . date('l');
echo $greeting . "<br>";
// Concatenation and arithmetic
$a = 5;
$b = 10;
$result = "The sum is: " . $a + $b; // Caution! This doesn't work as expected
echo $result . "<br>"; // Warning: A non-numeric value...
// Correct way with parentheses
$result = "The sum is: " . ($a + $b);
echo $result . "<br>"; // The sum is: 15
// Concatenation vs. addition
$num1 = "10";
$num2 = "20";
$result1 = $num1 + $num2; // Addition (strings converted to numbers)
$result2 = $num1 . $num2; // Concatenation
echo "$num1 + $num2 = $result1 (numeric addition)<br>"; // 30
echo "$num1 . $num2 = $result2 (string concatenation)<br>"; // 1020
?>
Caution: Be particularly careful when mixing string concatenation with other operators. Use parentheses to clearly indicate your intent.
4. Comparison and Logical Operators
Understanding the precedence between comparison and logical operators is crucial for writing correct conditional expressions:
<?php
// Comparison and logical operators
$a = 5;
$b = 10;
$c = 15;
// Logical operators precedence
$result1 = $a < $b && $b < $c;
echo "($a < $b) && ($b < $c) = " . ($result1 ? 'true' : 'false') . "<br>"; // true
// Comparison has higher precedence than logical operators
$result2 = $a < $b && $b > $c;
echo "($a < $b) && ($b > $c) = " . ($result2 ? 'true' : 'false') . "<br>"; // false
// AND vs OR precedence
$result3 = $a < $b || $b > $c && $a < $c;
// Evaluated as: $a < $b || ($b > $c && $a < $c)
echo "$a < $b || $b > $c && $a < $c = " . ($result3 ? 'true' : 'false') . "<br>"; // true
// Using parentheses for different logic
$result4 = ($a < $b || $b > $c) && $a < $c;
echo "($a < $b || $b > $c) && $a < $c = " . ($result4 ? 'true' : 'false') . "<br>"; // true
// Different result with different parentheses
$result5 = $a < $b || ($b > $c && $a < $c);
echo "$a < $b || ($b > $c && $a < $c) = " . ($result5 ? 'true' : 'false') . "<br>"; // true
?>
Key Points:
- Comparison operators (<, >, <=, >=, ==, ===, etc.) have higher precedence than logical operators
- && (logical AND) has higher precedence than || (logical OR)
- The word-form operators (and, or, xor) have lower precedence than their symbolic counterparts
5. Assignment Operators
Assignment operators have very low precedence, which can lead to some surprising behavior:
<?php
// Assignment operator precedence
$a = 5;
$b = 10;
// Assignment vs. comparison
if ($c = $a + $b) {
echo "This will execute because $c is 15 (truthy)<br>";
}
// Correct comparison (using ==)
if ($c == $a + $b) {
echo "This checks if $c equals " . ($a + $b) . "<br>";
}
// Multiple assignments
$x = $y = $z = 0;
echo "After $x = $y = $z = 0: x = $x, y = $y, z = $z<br>";
// Assignment in expressions
$total = 0;
$numbers = [1, 2, 3, 4, 5];
foreach ($numbers as $num) {
// The assignment happens, then the value is added to $total
echo "Total += " . ($total += $num) . "<br>";
}
?>
Caution: The low precedence of assignment operators is often the source of bugs, especially when mistakenly using = (assignment) instead of == (comparison) in conditional statements.
6. Different Precedence: Symbolic vs. Word Form Operators
PHP provides both symbolic (&&, ||) and word form (and, or) logical operators, with different precedence levels:
<?php
// Symbolic vs. word form operators
$a = true;
$b = false;
$c = true;
// && (higher precedence) vs. and (lower precedence)
$result1 = $a && $b; // $a && $b = false
$result2 = $a and $b; // Interpreted as: ($a) and $b, which is true and false = false
// This is where it gets tricky
$var1 = $a && $b; // $var1 = false
$var2 = $a and $b; // Interpreted as: ($var2 = $a) and $b, so $var2 = true!
echo "Using &&: " . ($var1 ? 'true' : 'false') . "<br>"; // false
echo "Using and: " . ($var2 ? 'true' : 'false') . "<br>"; // true!
// || (higher precedence) vs. or (lower precedence)
$var3 = $a || $b; // $var3 = true
$var4 = $a or $b; // Interpreted as: ($var4 = $a) or $b, so $var4 = true
echo "Using ||: " . ($var3 ? 'true' : 'false') . "<br>"; // true
echo "Using or: " . ($var4 ? 'true' : 'false') . "<br>"; // true (same result this time)
// Mixing with assignment (very tricky!)
$var5 = false || true; // $var5 = true
$var6 = false or true; // Interpreted as: ($var6 = false) or true, so $var6 = false!
echo "false || true results in: " . ($var5 ? 'true' : 'false') . "<br>"; // true
echo "false or true results in: " . ($var6 ? 'true' : 'false') . "<br>"; // false!
?>
Best Practice: For clarity and to avoid subtle bugs, prefer the symbolic operators (&&, ||) over the word forms (and, or). If you do use the word forms, always use parentheses to clarify your intent.
Operator Associativity
In addition to precedence, PHP operators also have associativity, which determines the order of evaluation when multiple operators of the same precedence appear in an expression.
Associativity Examples
<?php
// Left associativity examples
$a = 20;
$b = 10;
$c = 5;
// Subtraction is left-associative
$result1 = $a - $b - $c; // ($a - $b) - $c = (20 - 10) - 5 = 5
echo "$a - $b - $c = $result1<br>"; // 5
// Division is also left-associative
$result2 = $a / $b / $c; // ($a / $b) / $c = (20 / 10) / 5 = 0.4
echo "$a / $b / $c = $result2<br>"; // 0.4
// Right associativity examples
// Assignment is right-associative
$x = $y = $z = 5; // $x = ($y = ($z = 5))
echo "After $x = $y = $z = 5: x = $x, y = $y, z = $z<br>"; // All 5
// Exponentiation is right-associative
$result3 = 2 ** 3 ** 2; // 2 ** (3 ** 2) = 2 ** 9 = 512 (not (2 ** 3) ** 2 = 64)
echo "2 ** 3 ** 2 = $result3<br>"; // 512
// Non-associative examples
// Comparison operators are non-associative
$value = 5;
// This does NOT work as in mathematics: 1 < $value < 10
//$result4 = 1 < $value < 10; // PHP evaluates this as (1 < $value) < 10, which is (true) < 10
// Correct way to check if value is in range
$result5 = (1 < $value) && ($value < 10);
echo "Is $value between 1 and 10? " . ($result5 ? 'Yes' : 'No') . "<br>"; // Yes
?>
Important Notes on Associativity
- Left associative operators (like +, -, *, /, %, &, |, ^, &&, ||) group from left to right
- Right associative operators (like =, +=, -=, *=, /=, **) group from right to left
- Non-associative operators (like ==, !=, ===, !==, <, >, <=, >=) cannot be chained without parentheses
- Understanding associativity is particularly important when working with assignment operators and exponentiation
Common Pitfalls and Mistakes
Let's examine some common mistakes related to operator precedence and how to avoid them:
1. Assignment vs. Comparison
One of the most common errors in PHP is confusing the assignment operator (=) with the equality comparison operator (==):
<?php
// Assignment vs. comparison pitfall
$value = 10;
// WRONG: Assignment in condition
if ($result = 20) {
echo "This always executes because 20 is truthy<br>";
echo "And \$result now equals $result<br>";
}
// CORRECT: Comparison in condition
if ($value == 20) {
echo "This only executes if \$value equals 20<br>";
} else {
echo "\$value does not equal 20<br>";
}
// The assignment error is especially common in loops
$i = 0;
// WRONG: Infinite loop because we're assigning, not comparing
/*
while ($i = 1) {
echo "This will run forever!";
}
*/
// CORRECT: Comparison in loop condition
while ($i == 0) {
echo "This runs once<br>";
$i++;
}
?>
Prevention: Many developers adopt the practice of putting the constant value on the left side of comparisons (e.g., if (20 == $value)), which would cause a syntax error if you accidentally used = instead of ==. Modern IDEs will also warn you about this issue.
2. Logical Operator Precedence Confusion
Misunderstanding the precedence of logical operators can lead to unexpected behavior:
<?php
// Logical operator precedence pitfall
$a = true;
$b = false;
$c = true;
// WRONG: Assuming OR and AND have the same precedence
$result1 = $a || $b && $c;
echo "Using $a || $b && $c without parentheses: " . ($result1 ? 'true' : 'false') . "<br>";
// This evaluates as: $a || ($b && $c) because && has higher precedence
// CORRECT: Use parentheses to control evaluation order
$result2 = ($a || $b) && $c;
echo "Using ($a || $b) && $c with parentheses: " . ($result2 ? 'true' : 'false') . "<br>";
// WRONG: Mixing word forms and symbols without understanding precedence
$var = false;
$result3 = $var or true;
echo "Using 'false or true' in an assignment: " . ($result3 ? 'true' : 'false') . "<br>";
// This evaluates as: ($var = false) or true, so $result3 is false!
// CORRECT: Use parentheses for clarity
$result4 = false || true; // or: $result4 = (false or true);
echo "Using 'false || true' correctly: " . ($result4 ? 'true' : 'false') . "<br>";
?>
Prevention: Always use parentheses to explicitly define the intended order of operations in complex logical expressions. Prefer using the symbolic operators (&&, ||) instead of the word forms (and, or).
3. Ternary Operator Gotchas
The ternary operator can be tricky, especially when nested:
<?php
// Ternary operator pitfalls
$a = 5;
$b = 10;
$c = 15;
// Simple usage is straightforward
$result1 = ($a < $b) ? "a is less than b" : "a is not less than b";
echo "Simple ternary: $result1<br>";
// WRONG: Nesting without parentheses can be confusing
$result2 = $a < $b ? $a : $b < $c ? $b : $c;
echo "Nested ternary without parentheses: $result2<br>";
// This might not work as expected due to left associativity
// CORRECT: Use parentheses for nested ternary operators
$result3 = $a < $b ? $a : ($b < $c ? $b : $c);
echo "Nested ternary with parentheses: $result3<br>";
// PHP 7.4+ warning: Unparenthesized ternary operations are deprecated
// Commonly seen abbreviated version
$name = null;
$display_name = $name ?: "Guest"; // Shorthand for $name ? $name : "Guest"
echo "Display name: $display_name<br>";
// PHP 7+ null coalescing operator (different precedence!)
$username = $_GET['username'] ?? "Guest";
echo "Username: $username<br>";
?>
Prevention: Always use parentheses with nested ternary operations to clarify the intended behavior. Consider using if-else statements instead for complex conditions, as they're often more readable.
4. Concatenation and Addition Confusion
Mixing string concatenation with arithmetic operations can lead to unexpected results:
<?php
// Concatenation vs. addition pitfall
$a = 5;
$b = 10;
// WRONG: Concatenation operator has same precedence as addition
$result1 = "Sum: " . $a + $b; // Parsed as: ("Sum: " . $a) + $b
// This gives a warning or error because "Sum: 5" is not a valid number
// CORRECT: Use parentheses to specify operation order
$result2 = "Sum: " . ($a + $b);
echo "Correctly formatted: $result2<br>"; // Sum: 15
// WRONG: String with a number
$str = "42";
$result3 = $str + 10; // PHP converts "42" to int 42, resulting in 52
echo "String '42' + 10 = $result3<br>"; // 52 (numeric addition)
// CORRECT: For string concatenation
$result4 = $str . 10;
echo "String '42' . 10 = $result4<br>"; // "4210" (string concatenation)
?>
Prevention: Always use parentheses around arithmetic operations that are part of string concatenation. Be explicit about your intentions when working with strings that might be interpreted as numbers.
Best Practices
Follow these guidelines to write code that is both correct and clear regarding operator precedence:
- Use parentheses liberally: Even when not strictly necessary, parentheses make your code more readable and clarify your intentions.
- Break complex expressions into simpler ones: Instead of writing one complex expression, break it down into multiple steps with intermediate variables.
- Comment complicated expressions: Add comments to explain the logic behind complex expressions, especially when they involve multiple operator types.
- Be consistent: Use a consistent style for operator spacing and parentheses throughout your codebase.
- Use symbolic logical operators: Prefer && and || over and and or to avoid precedence confusion.
- Be explicit about type conversions: Use explicit type casting ((int), (string), etc.) rather than relying on implicit conversion.
- Use comparison operators correctly: Double-check that you're using == for comparison and = for assignment.
- Consider using named constants: For complex expressions that appear multiple times, define a named constant to improve readability.
Best Practices Example
<?php
// A real-world function that applies best practices
/**
* Calculate the total price for an order
*
* @param array $items Array of items with 'price' and 'quantity' keys
* @param float $tax_rate Tax rate (e.g., 0.07 for 7%)
* @param bool $apply_discount Whether to apply a discount
* @param float $discount_rate Discount rate (default 0.10 for 10%)
* @return array Order details including subtotal, tax, discount, and total
*/
function calculateOrderTotal(array $items, float $tax_rate, bool $apply_discount = false, float $discount_rate = 0.10): array {
// Initialize totals
$subtotal = 0.0;
// Calculate subtotal from items
foreach ($items as $item) {
// Use parentheses for clarity, even when not strictly necessary
$item_total = (float)$item['price'] * (int)$item['quantity'];
$subtotal += $item_total;
}
// Calculate discount (if applicable)
$discount = 0.0;
if ($apply_discount && $subtotal > 0) {
// Calculate discount amount
$discount = $subtotal * $discount_rate;
}
// Apply discount
$discounted_subtotal = $subtotal - $discount;
// Calculate tax
$tax = $discounted_subtotal * $tax_rate;
// Calculate final total
$total = $discounted_subtotal + $tax;
// Return order details
return [
'subtotal' => $subtotal,
'discount' => $discount,
'tax' => $tax,
'total' => $total
];
}
// Usage example
$order_items = [
['name' => 'Widget', 'price' => 19.99, 'quantity' => 2],
['name' => 'Gadget', 'price' => 29.99, 'quantity' => 1],
['name' => 'Doohickey', 'price' => 9.99, 'quantity' => 3]
];
$tax_rate = 0.07; // 7% tax
$apply_discount = true;
// Calculate order total
$order_summary = calculateOrderTotal($order_items, $tax_rate, $apply_discount);
// Display order summary
echo "<h3>Order Summary</h3>";
echo "Subtotal: $" . number_format($order_summary['subtotal'], 2) . "<br>";
echo "Discount: $" . number_format($order_summary['discount'], 2) . "<br>";
echo "Tax: $" . number_format($order_summary['tax'], 2) . "<br>";
echo "Total: $" . number_format($order_summary['total'], 2) . "<br>";
// Alternative approach for a complicated expression
// INSTEAD OF:
// $complex_value = $a * $b + $c / ($d - $e) * ($f + $g);
// Break it down:
function calculateComplexValue($a, $b, $c, $d, $e, $f, $g) {
// Calculate each part separately
$part1 = $a * $b;
// Use clear parentheses for division
$denominator = ($d - $e);
// Check for division by zero
if ($denominator !== 0) {
$part2 = $c / $denominator;
} else {
// Handle division by zero
$part2 = 0; // Or throw an exception
}
$part3 = ($f + $g);
// Combine the parts
$result = $part1 + ($part2 * $part3);
return $result;
}
?>
Real-World Examples
Let's look at some real-world scenarios where understanding operator precedence is crucial:
1. Conditional Logic in User Authentication
<?php
// User authentication example
function authenticateUser($username, $password, $remember_me = false) {
// Simulate database lookup
$user = findUserByUsername($username);
// WRONG approach - precedence issues
if (!$user || !verifyPassword($user, $password) && !isSocialLoginValid($user))
return false;
// CORRECT approach with explicit parentheses
if ((!$user) || ((!verifyPassword($user, $password)) && (!isSocialLoginValid($user))))
return false;
// Even better: break it down for readability
if (!$user) {
// User not found
return false;
}
// Check credentials
$password_valid = verifyPassword($user, $password);
$social_login_valid = isSocialLoginValid($user);
// User must have either valid password or valid social login
if (!$password_valid && !$social_login_valid) {
return false;
}
// Authentication successful
if ($remember_me) {
setRememberMeCookie($user);
}
return true;
}
// Simulation functions for the example
function findUserByUsername($username) {
// Simulate user lookup
if ($username === 'admin') {
return ['id' => 1, 'username' => 'admin', 'password_hash' => 'hash_here'];
}
return null;
}
function verifyPassword($user, $password) {
// Simulate password verification
return $password === 'password';
}
function isSocialLoginValid($user) {
// Simulate social login check
return false;
}
function setRememberMeCookie($user) {
// Simulate setting a cookie
echo "Remember-me cookie set for user {$user['username']}<br>";
}
// Test the authentication
$auth_result = authenticateUser('admin', 'password', true);
echo "Authentication result: " . ($auth_result ? 'Success' : 'Failure') . "<br>";
?>
2. Data Filtering and Validation
<?php
// Data filtering example
function filterProducts($products, $category = null, $min_price = null, $max_price = null, $in_stock_only = false) {
$filtered = [];
foreach ($products as $product) {
// WRONG: Precedence confusion
if ($category !== null && $product['category'] !== $category ||
$min_price !== null && $product['price'] < $min_price ||
$max_price !== null && $product['price'] > $max_price ||
$in_stock_only && $product['stock'] <= 0)
continue;
// CORRECT: Proper use of parentheses
if (($category !== null && $product['category'] !== $category) ||
($min_price !== null && $product['price'] < $min_price) ||
($max_price !== null && $product['price'] > $max_price) ||
($in_stock_only && $product['stock'] <= 0))
continue;
// Even better: break it down for readability
// Skip if category doesn't match
if ($category !== null && $product['category'] !== $category) {
continue;
}
// Skip if price is too low
if ($min_price !== null && $product['price'] < $min_price) {
continue;
}
// Skip if price is too high
if ($max_price !== null && $product['price'] > $max_price) {
continue;
}
// Skip if out of stock (when in_stock_only is true)
if ($in_stock_only && $product['stock'] <= 0) {
continue;
}
// Product passed all filters
$filtered[] = $product;
}
return $filtered;
}
// Sample product data
$products = [
['id' => 1, 'name' => 'Laptop', 'category' => 'electronics', 'price' => 899.99, 'stock' => 5],
['id' => 2, 'name' => 'Smartphone', 'category' => 'electronics', 'price' => 499.99, 'stock' => 0],
['id' => 3, 'name' => 'Headphones', 'category' => 'accessories', 'price' => 99.99, 'stock' => 10],
['id' => 4, 'name' => 'Tablet', 'category' => 'electronics', 'price' => 299.99, 'stock' => 3],
['id' => 5, 'name' => 'Mouse', 'category' => 'accessories', 'price' => 29.99, 'stock' => 20],
];
// Filter electronics between $200 and $700 that are in stock
$filtered = filterProducts($products, 'electronics', 200, 700, true);
// Display results
echo "<h3>Filtered Products</h3>";
echo "<ul>";
foreach ($filtered as $product) {
echo "<li>{$product['name']} - ${$product['price']} ({$product['stock']} in stock)</li>";
}
echo "</ul>";
?>
3. Dynamic SQL Query Building
<?php
// Dynamic SQL query building
function buildSearchQuery($search_terms, $filters = []) {
$base_query = "SELECT * FROM products WHERE 1=1";
$params = [];
// Add search terms
if (!empty($search_terms)) {
// WRONG: Operator precedence confusion
$base_query .= " AND (name LIKE ? OR description LIKE ?" .
!empty($filters['search_sku']) ? " OR sku LIKE ?" : "" . ")";
// CORRECT: Use parentheses for string concatenation
$base_query .= " AND (name LIKE ? OR description LIKE ?" .
((!empty($filters['search_sku'])) ? " OR sku LIKE ?" : "") . ")";
$search_pattern = "%" . $search_terms . "%";
$params[] = $search_pattern;
$params[] = $search_pattern;
if (!empty($filters['search_sku'])) {
$params[] = $search_pattern;
}
}
// Apply category filter
if (!empty($filters['category'])) {
$base_query .= " AND category = ?";
$params[] = $filters['category'];
}
// Apply price range filter
if (!empty($filters['min_price']) || !empty($filters['max_price'])) {
if (!empty($filters['min_price'])) {
$base_query .= " AND price >= ?";
$params[] = (float) $filters['min_price'];
}
if (!empty($filters['max_price'])) {
$base_query .= " AND price <= ?";
$params[] = (float) $filters['max_price'];
}
}
// Apply in-stock filter
if (!empty($filters['in_stock']) && $filters['in_stock']) {
$base_query .= " AND stock > 0";
}
// Apply sorting
$base_query .= " ORDER BY " . (!empty($filters['sort_by']) ? $filters['sort_by'] : "name") .
" " . (!empty($filters['sort_dir']) && strtoupper($filters['sort_dir']) === "DESC" ? "DESC" : "ASC");
// Add pagination
if (!empty($filters['page']) && !empty($filters['limit'])) {
$page = max(1, (int) $filters['page']);
$limit = max(1, (int) $filters['limit']);
$offset = ($page - 1) * $limit;
$base_query .= " LIMIT ?, ?";
$params[] = $offset;
$params[] = $limit;
}
return [
'query' => $base_query,
'params' => $params
];
}
// Example filters
$filters = [
'category' => 'electronics',
'min_price' => 100,
'max_price' => 500,
'in_stock' => true,
'sort_by' => 'price',
'sort_dir' => 'asc',
'page' => 1,
'limit' => 10,
'search_sku' => true
];
// Build a search query
$search_result = buildSearchQuery("phone", $filters);
// Display the query
echo "<h3>Generated SQL Query</h3>";
echo "<p>" . htmlspecialchars($search_result['query']) . "</p>";
echo "<h4>Parameters:</h4>";
echo "<pre>";
print_r($search_result['params']);
echo "</pre>";
?>
Practice Exercises
Test your understanding of PHP operator precedence with these exercises:
Exercise 1: Predict the Output
Without running the code, predict the output of the following PHP snippets:
<?php
// Expression 1
$result1 = 5 + 3 * 2 - 4 / 2;
// Expression 2
$a = true;
$b = false;
$c = true;
$result2 = $a && $b || $c;
// Expression 3
$x = 10;
$y = 5;
$z = 2;
$result3 = $x / $y * $z;
// Expression 4
$str = "10";
$num = 5;
$result4 = $str + $num . " items";
// Expression 5
$val = null;
$default = "Default";
$result5 = $val ?? $default . " Value";
?>
After predicting, write a PHP script to verify your answers and explain why each expression produces its result.
Exercise 2: Fix the Bugs
The following code contains bugs related to operator precedence. Identify and fix each issue:
<?php
// Bug 1: Logical operator confusion
function isValidInput($value, $min, $max) {
return $value >= $min && $value <= $max || $value == 0;
}
// Bug 2: Assignment in condition
function processUser($user) {
if ($status = getUserStatus($user)) {
echo "User is active";
}
return $status;
}
// Bug 3: String concatenation issue
function formatPrice($price, $currency) {
return "Price: " . $currency . $price * 1.1; // Add 10% tax
}
// Bug 4: Ternary operator nesting
function getDisplayName($user) {
return $user->first_name ? $user->first_name : $user->username ? $user->username : "Guest";
}
// Bug 5: Bitwise vs. logical operators
function hasPermission($user, $permission) {
$admin_role = 1;
$editor_role = 2;
return $user->role & $admin_role || $user->role & $editor_role && $permission != "delete";
}
// Implement your fixed versions of each function
?>
Exercise 3: Create a Complex Expression
Write a PHP function that calculates the total cost of an order based on the following rules:
- Base price is the sum of all items (price * quantity)
- Apply a 10% discount if the total is over $100
- Apply an additional 5% discount if the customer is a member
- Add 8% tax after discounts
- Add $5 shipping fee for orders under $50 (after discount, before tax)
Write this function in two ways:
- As a single complex expression with careful use of parentheses
- Using a step-by-step approach with intermediate variables
Compare the readability and maintainability of both approaches.
Summary
In this session, we've explored PHP's operator precedence and associativity, which determine the order in which operators are evaluated in expressions:
- Operator Precedence: Defines which operators have higher priority and are evaluated first in expressions with multiple operators.
- Operator Associativity: Determines the order of evaluation for operators with the same precedence level (left-to-right or right-to-left).
- Key Precedence Groups: Parentheses have highest precedence, followed by arithmetic operators, string concatenation, comparisons, logical operators, and finally assignments.
- Common Pitfalls: We've identified frequent mistakes related to precedence, such as confusing assignment with comparison, misunderstanding logical operator precedence, and mixing operators without proper parentheses.
- Best Practices: Use parentheses liberally, break complex expressions into simpler ones, and be explicit about your intentions to write code that is both correct and clear.
Understanding operator precedence is fundamental to writing reliable PHP code. By mastering these concepts, you can avoid subtle bugs and create expressions that accurately reflect your intentions. Remember, even when the default precedence would give the expected result, using parentheses improves code readability and makes your code more maintainable.