Skip to content

01: Types, Variables & Operators

Types and Variables Hero

Beginner 60-75 min

Coming from Java’s strongly-typed world, PHP’s dynamic typing might seem foreign at first. However, modern PHP (8.0+) offers optional static typing that brings it closer to Java’s type safety. PHP 8.4 includes advanced type features like union types, intersection types, and improved type inference. In this chapter, we’ll explore PHP’s type system, understand how variables work, and learn operators—all while comparing to Java equivalents.

By the end of this chapter, you’ll understand how to write type-safe PHP code that feels familiar to your Java background.

::: info Time Estimate ⏱️ 60-75 minutes to complete this chapter :::

What you need:

In this chapter, you’ll create:

  • A type-safe data validation class
  • A utility library for type conversions
  • A demonstration of PHP’s type juggling vs strict types
  • Operator examples comparing PHP and Java (including bitwise operations)
  • Constants and configuration examples
  • Scope demonstration scripts
  • Reference-based utility functions

By the end of this chapter, you’ll be able to:

  • Understand PHP’s type system and how it differs from Java
  • Use type declarations to write safer PHP code
  • Work with PHP’s scalar types (int, float, string, bool)
  • Leverage compound types (arrays, objects, callables)
  • Apply strict typing to catch type errors early
  • Use operators effectively including PHP-specific ones
  • Define and use constants (global and class-level)
  • Understand variable scope (global, local, static)
  • Work with references (pass-by-reference vs pass-by-value)
  • Apply bitwise operators for flags and low-level operations

Understand PHP’s dynamic typing and how strict typing brings it closer to Java.

::: code-group

// Java requires type declarations
String name = "Alice";
int age = 30;
double price = 99.99;
// Type checking happens at compile time
name = 123; // ❌ Compile error: incompatible types
<?php
// PHP allows dynamic types
$name = "Alice";
$age = 30;
$price = 99.99;
// Type can change (type juggling)
$name = 123; // ✅ Allowed by default
var_dump($name); // int(123)
<?php
declare(strict_types=1); // Enable strict type checking
// With type declarations
$name = "Alice"; // string
$age = 30; // int
$price = 99.99; // float
function greet(string $name): void {
echo "Hello, $name!";
}
greet(123); // ❌ TypeError: must be of type string

:::

AspectJavaPHP (Weak Mode)PHP (Strict Mode)
Type DeclarationRequiredOptionalOptional
Type CheckingCompile-timeRuntimeRuntime
Type CoercionLimited (widening)Automatic (juggling)Minimal
Error DetectionCompile errorType error at runtimeType error at runtime
SafetyHighLowHigh

::: tip Recommendation for Java Developers Always use declare(strict_types=1); at the top of your PHP files. This gives you type safety similar to Java and catches errors early. :::

PHP’s dynamic nature is powerful for scripting, but strict typing prevents bugs like:

  • Accidentally passing wrong types to functions
  • Type confusion in calculations
  • Silent type conversions leading to unexpected behavior

Master PHP’s scalar types and their Java equivalents.

PHP has 4 scalar types that map to Java primitives:

::: code-group

<?php
declare(strict_types=1);
// int - Integer numbers
$count = 42;
$negative = -10;
$hex = 0xFF; // 255
$binary = 0b1010; // 10
$octal = 0755; // 493
// float (aka double) - Floating point numbers
$price = 99.99;
$scientific = 1.23e4; // 12300.0
$pi = 3.14159;
// string - Text data
$name = "Alice";
$message = 'Hello, World!';
$multiline = "Line 1\nLine 2";
// bool - Boolean values
$isActive = true;
$hasError = false;
// int - Integer numbers
int count = 42;
int negative = -10;
int hex = 0xFF; // 255
int binary = 0b1010; // 10
int octal = 0755; // 493
// double - Floating point numbers
double price = 99.99;
double scientific = 1.23e4; // 12300.0
double pi = 3.14159;
// String - Text data (not a primitive!)
String name = "Alice";
String message = "Hello, World!";
String multiline = "Line 1\nLine 2";
// boolean - Boolean values
boolean isActive = true;
boolean hasError = false;

:::

::: code-group

<?php
declare(strict_types=1);
function calculateTotal(float $price, int $quantity): float
{
return $price * $quantity;
}
function formatMessage(string $template, string $name): string
{
return str_replace('{name}', $name, $template);
}
function isEligible(int $age, bool $hasLicense): bool
{
return $age >= 18 && $hasLicense;
}
// Usage
$total = calculateTotal(19.99, 3); // float(59.97)
$msg = formatMessage("Hello, {name}!", "Alice");
$eligible = isEligible(25, true); // bool(true)
public class Example {
public static double calculateTotal(double price, int quantity) {
return price * quantity;
}
public static String formatMessage(String template, String name) {
return template.replace("{name}", name);
}
public static boolean isEligible(int age, boolean hasLicense) {
return age >= 18 && hasLicense;
}
// Usage
public static void main(String[] args) {
double total = calculateTotal(19.99, 3);
String msg = formatMessage("Hello, {name}!", "Alice");
boolean eligible = isEligible(25, true);
}
}

:::

PHP’s string handling is more powerful and flexible than Java’s:

::: code-group

<?php
// Double quotes: variable interpolation
$name = "Alice";
$greeting = "Hello, $name!"; // "Hello, Alice!"
$complex = "Age: {$user->getAge()}"; // Can call methods
// Single quotes: literal strings (like Java)
$literal = 'Hello, $name!'; // "Hello, $name!" (no interpolation)
// String concatenation
$full = $firstName . " " . $lastName; // Use dot operator
// Heredoc (like Java text blocks)
$html = <<<HTML
<div class="user">
<h1>$name</h1>
<p>Welcome back!</p>
</div>
HTML;
// Nowdoc (no interpolation, like Java text blocks)
$raw = <<<'TEXT'
Raw text with $variables not interpreted
TEXT;
// String concatenation (verbose)
String name = "Alice";
String greeting = "Hello, " + name + "!";
// String.format() for interpolation
String formatted = String.format("Hello, %s!", name);
// Text blocks (Java 15+)
String html = """
<div class="user">
<h1>%s</h1>
<p>Welcome back!</p>
</div>
""".formatted(name);
// No equivalent to PHP's variable interpolation
// No equivalent to calling methods in strings

:::

::: warning Key String Differences

  1. PHP: Double quotes interpolate variables, single quotes don’t
  2. PHP: Use . for concatenation (not +)
  3. PHP: Can call methods inside strings: "{$obj->method()}"
  4. Java: No built-in variable interpolation
  5. Java: Use + for concatenation :::

Understand PHP constants and how they differ from Java’s final variables.

PHP supports two ways to define constants:

::: code-group

<?php
declare(strict_types=1);
// Method 1: define() function (runtime)
define('APP_NAME', 'My Application');
define('MAX_USERS', 100);
define('PI', 3.14159);
// Method 2: const keyword (compile-time, PHP 5.3+)
const APP_VERSION = '1.0.0';
const DEBUG_MODE = true;
// Constants are case-sensitive by default
define('SITE_URL', 'https://example.com');
// Access constants (no $ prefix!)
echo APP_NAME; // "My Application"
echo MAX_USERS; // 100
echo APP_VERSION; // "1.0.0"
// Check if constant exists
if (defined('APP_NAME')) {
echo "APP_NAME is defined";
}
// Constants cannot be redefined
define('APP_NAME', 'New Name'); // ⚠️ Warning: Constant already defined
// Constants are global by default
function showAppName(): void {
echo APP_NAME; // Works - constants are accessible everywhere
}
// Java uses final keyword
public class Constants {
// Class constants (static final)
public static final String APP_NAME = "My Application";
public static final int MAX_USERS = 100;
public static final double PI = 3.14159;
// Instance constants (final)
private final String appVersion = "1.0.0";
// Method to access
public void showAppName() {
System.out.println(APP_NAME); // Access via class name
}
}
// Access constants
System.out.println(Constants.APP_NAME); // "My Application"

:::

AspectJavaPHP
Syntaxstatic final TYPE NAME = value;define('NAME', value); or const NAME = value;
ScopeClass-level or instance-levelGlobal by default
AccessClassName.CONSTANT or instance.constantDirect name (no prefix)
TypeMust declare typeInferred from value
RedefinitionCompile errorRuntime warning (ignored)
Case SensitivityCase-sensitiveCase-sensitive (can use define() with 3rd param)
<?php
declare(strict_types=1);
// Use define() for:
// - Conditional constants
if ($environment === 'production') {
define('DEBUG_MODE', false);
} else {
define('DEBUG_MODE', true);
}
// - Constants in functions/conditionals
function setupConstants(): void {
define('DYNAMIC_CONSTANT', getValueFromConfig());
}
// Use const for:
// - Class constants (covered in Chapter 3)
// - Top-level constants (always available)
const API_VERSION = '2.0';
const MAX_RETRIES = 3;
// - Better performance (compile-time)
const CACHE_TTL = 3600;

::: tip Best Practice for Java Developers

  • Use const for simple, always-available constants (like Java’s static final)
  • Use define() only when you need conditional or dynamic constant definition
  • Prefer class constants (Chapter 3) for better organization
  • Constants are global - be careful with naming to avoid conflicts :::

PHP provides special constants that change based on context:

<?php
declare(strict_types=1);
// File and line information
echo __FILE__; // Current file path
echo __LINE__; // Current line number
echo __DIR__; // Directory of current file
// Function and class information
function example(): void {
echo __FUNCTION__; // "example"
echo __METHOD__; // "example" (or "ClassName::example" in class)
}
class Example {
public function method(): void {
echo __CLASS__; // "Example"
echo __METHOD__; // "Example::method"
echo __NAMESPACE__; // Namespace (if any)
}
}

Java equivalent:

// Java doesn't have magic constants
// Use reflection or stack traces for similar info
public class Example {
public void method() {
String className = this.getClass().getName();
String methodName = Thread.currentThread()
.getStackTrace()[1].getMethodName();
}
}

Understand PHP’s variable scope and how it differs from Java’s scoping rules.

PHP has different scoping rules than Java:

::: code-group

<?php
declare(strict_types=1);
// Global scope
$globalVar = "I'm global";
function testFunction(): void {
// Local scope - $globalVar is NOT accessible here
$localVar = "I'm local";
echo $localVar; // ✅ Works
// echo $globalVar; // ❌ Undefined variable error!
// Access global variable using 'global' keyword
global $globalVar;
echo $globalVar; // ✅ Now works
// Or use $GLOBALS superglobal
echo $GLOBALS['globalVar']; // ✅ Also works
}
// Static variables (persist between function calls)
function counter(): int {
static $count = 0; // Initialized only once
$count++;
return $count;
}
echo counter(); // 1
echo counter(); // 2
echo counter(); // 3
// Each function call maintains its own static variable
function anotherCounter(): int {
static $count = 0;
$count += 10;
return $count;
}
echo anotherCounter(); // 10 (separate from counter()'s $count)
public class ScopeExample {
// Instance variable (class scope)
private String instanceVar = "I'm an instance variable";
// Class variable (static)
private static String classVar = "I'm a class variable";
public void testMethod() {
// Local variable
String localVar = "I'm local";
System.out.println(localVar); // ✅ Works
// Instance variable accessible
System.out.println(this.instanceVar); // ✅ Works
// Class variable accessible
System.out.println(ScopeExample.classVar); // ✅ Works
}
// Static method
public static void staticMethod() {
// Only static/class variables accessible
System.out.println(classVar); // ✅ Works
// System.out.println(instanceVar); // ❌ Error - no instance
}
}

:::

The global keyword imports a global variable into local scope:

<?php
declare(strict_types=1);
$name = "Alice";
$age = 30;
function greet(): void {
global $name, $age; // Import global variables
echo "Hello, $name! You are $age years old.";
}
greet(); // "Hello, Alice! You are 30 years old."
// Modifying global variables
function incrementAge(): void {
global $age;
$age++; // Modifies the global $age
}
incrementAge();
echo $age; // 31

::: warning Avoid Global Variables Global variables make code harder to maintain and test. Prefer:

  • Function parameters
  • Return values
  • Dependency injection (covered in Chapter 11)
  • Class properties (covered in Chapter 3) :::

$GLOBALS is an associative array containing all global variables:

<?php
declare(strict_types=1);
$user = "Alice";
$count = 5;
function accessGlobals(): void {
// Access via $GLOBALS array
echo $GLOBALS['user']; // "Alice"
echo $GLOBALS['count']; // 5
// Modify global variables
$GLOBALS['count']++;
}
accessGlobals();
echo $count; // 6

Static variables persist between function calls but remain local to the function:

<?php
declare(strict_types=1);
function getNextId(): int {
static $id = 0; // Initialized only on first call
$id++;
return $id;
}
echo getNextId(); // 1
echo getNextId(); // 2
echo getNextId(); // 3
// Useful for caching/memoization
function expensiveCalculation(int $input): int {
static $cache = [];
if (isset($cache[$input])) {
return $cache[$input];
}
// Expensive operation
$result = $input * $input * 1000;
$cache[$input] = $result;
return $result;
}

Java equivalent:

public class Example {
private static int id = 0; // Class-level static
public static int getNextId() {
return ++id; // Shared across all calls
}
// No function-level static in Java
// Use class-level static or instance variables
}
Scope TypePHPJavaNotes
Global$var (top-level)Class/package levelPHP globals accessible via global keyword
LocalInside functionInside method/blockBoth are function/method-scoped
Static (function)static $varNot availablePHP-only feature
Static (class)static $propertystatic keywordCovered in Chapter 3
Instance$this->propertythis.fieldCovered in Chapter 3

Understand PHP’s reference system and how it differs from Java’s pass-by-value model.

PHP supports both pass-by-value (default) and pass-by-reference (with &):

::: code-group

<?php
declare(strict_types=1);
// Pass-by-value (default)
function increment(int $value): void {
$value++; // Only modifies local copy
}
$number = 5;
increment($number);
echo $number; // Still 5 (unchanged)
// Pass-by-reference (use &)
function incrementByRef(int &$value): void {
$value++; // Modifies original variable
}
$number = 5;
incrementByRef($number);
echo $number; // 6 (modified!)
// Reference assignment
$a = 5;
$b = &$a; // $b is a reference to $a
$b = 10;
echo $a; // 10 (both point to same value)
// Unset reference
unset($b); // Only removes $b, $a still exists
echo $a; // 10
// Returning references
function &getValue(): int {
static $value = 0;
return $value; // Return reference
}
$ref = &getValue();
$ref = 100;
echo getValue(); // 100
// Java always passes by value
public class Example {
public static void increment(int value) {
value++; // Only modifies local copy
}
public static void incrementObject(MyObject obj) {
obj.value++; // Modifies object's field (object reference is copied, but points to same object)
}
public static void main(String[] args) {
int number = 5;
increment(number);
System.out.println(number); // Still 5
MyObject obj = new MyObject(5);
incrementObject(obj);
System.out.println(obj.value); // 6 (object modified)
}
}

:::

References are useful for:

  1. Modifying function parameters:
<?php
declare(strict_types=1);
function swap(int &$a, int &$b): void {
$temp = $a;
$a = $b;
$b = $temp;
}
$x = 10;
$y = 20;
swap($x, $y);
echo "$x, $y"; // "20, 10"
  1. Avoiding expensive copies:
<?php
declare(strict_types=1);
function processLargeArray(array &$data): void {
// Modifies array in place (no copy)
$data['processed'] = true;
}
$bigArray = [/* large dataset */];
processLargeArray($bigArray); // Efficient - no copy
  1. Returning references (rare):
<?php
declare(strict_types=1);
class Registry {
private static array $data = [];
public static function &get(string $key): mixed {
return self::$data[$key]; // Return reference
}
}
<?php
declare(strict_types=1);
// Reference in foreach
$array = [1, 2, 3];
foreach ($array as &$value) {
$value *= 2; // Modifies original array
}
unset($value); // ⚠️ Important: unset reference after loop!
print_r($array); // [2, 4, 6]
// Without unset, $value still references last element
$array[] = 4;
// $value would now reference the new element!
// References and arrays
$a = 1;
$b = 2;
$array = [&$a, &$b]; // Array contains references
$array[0] = 10;
echo $a; // 10 (modified through reference)

::: warning References Best Practices

  1. Avoid global references - Makes code hard to follow
  2. Unset foreach references - Prevents unexpected behavior
  3. Document reference parameters - Use PHPDoc @param int &$value
  4. Prefer return values - Usually clearer than modifying by reference
  5. Use sparingly - References can make code harder to understand :::

PHP supports variable variables (using variable name as another variable’s name):

<?php
declare(strict_types=1);
$name = "Alice";
$$name = "Hello"; // Creates $Alice = "Hello"
echo $Alice; // "Hello"
// Useful for dynamic variable access
$field = "email";
$user = [
"name" => "Alice",
"email" => "alice@example.com"
];
echo $user[$field]; // "alice@example.com"
// Variable variables with arrays
$foo = "bar";
$bar = "baz";
echo $$foo; // "baz" ($$foo = $bar)
// Variable function names
$func = "strtoupper";
echo $func("hello"); // "HELLO"

::: tip Variable Variables Use Cases

  • Dynamic property access (though arrays are usually better)
  • Configuration mapping
  • Template systems
  • Generally avoid - Makes code harder to read and debug :::

Understand PHP’s type juggling and why strict typing is better.

Without declare(strict_types=1), PHP automatically converts types:

type-juggling-example.php
<?php
// NO strict_types declaration
function add($a, $b) {
return $a + $b;
}
// Type juggling in action
echo add(5, 3); // 8 (int + int)
echo add(5, "3"); // 8 (int + string → int)
echo add("5", "3"); // 8 (string + string → int)
echo add(5, "3 apples"); // 8 ⚠️ (converts "3 apples" to 3!)
// Comparison type juggling
var_dump(0 == ""); // bool(true) ⚠️
var_dump(0 == "0"); // bool(true) ⚠️
var_dump(1 == "1"); // bool(true)
var_dump(1 == true); // bool(true) ⚠️
var_dump("10" == 10); // bool(true)

::: danger Type Juggling Dangers Type juggling can cause unexpected bugs:

  • "5" + "3" = 8 (not "53")
  • "10 apples" + 5 = 15 (silently drops “apples”)
  • 0 == false = true (confusing comparisons)
  • "" == 0 = true (empty string equals zero!) :::

With declare(strict_types=1), PHP behaves more like Java:

strict-typing-example.php
<?php
declare(strict_types=1);
function add(int $a, int $b): int {
return $a + $b;
}
echo add(5, 3); // ✅ 8
echo add(5, "3"); // ❌ TypeError: Argument #2 must be of type int, string given
echo add("5", "3"); // ❌ TypeError
echo add(5, 3.5); // ❌ TypeError: Argument #2 must be of type int, float given
// Strict comparison (always use ===)
var_dump(0 === ""); // bool(false) ✅
var_dump(1 === true); // bool(false) ✅
var_dump("10" === 10); // bool(false) ✅

::: tip Best Practice for Java Developers Always use:

  1. declare(strict_types=1); at the top of every PHP file
  2. Type hints for all function parameters and return values
  3. === for comparisons (strict equality, like Java’s ==)
  4. !== instead of != (strict inequality) :::

Here’s a quick reference for how PHP converts types in weak mode (avoid these scenarios by using strict types):

ExpressionWeak Mode ResultStrict Mode ResultExplanation
"5" + 38 (int)TypeErrorString “5” coerced to int 5
"10 apples" + 515 (int)TypeErrorString parsed to 10, rest ignored
true + true2 (int)TypeErrortrue = 1, false = 0
"hello" + 55 (int)TypeErrorNon-numeric string = 0
null + 55 (int)TypeErrornull = 0 in arithmetic
[] + [][] (array)[] (array)Array union (empty result)
0 == ""truefalse with ===Both are falsy
"0" == 0truefalse with ===String coerced to int
"00" == 0truefalse with ===String coerced to int
false == ""truefalse with ===Both are falsy

::: danger Common Pitfall

// Without strict types
function calculateDiscount($price, $percent) {
return $price - ($price * $percent / 100);
}
calculateDiscount("100", "10"); // Works but wrong!
calculateDiscount("100 dollars", "10%"); // Returns 90 (silently ignores text!)

Solution: Always use strict types!

declare(strict_types=1);
function calculateDiscount(float $price, float $percent): float {
return $price - ($price * $percent / 100);
}
calculateDiscount("100", "10"); // TypeError - caught immediately! ✅

:::

Here’s a script to see type behavior in action:

type-demonstration.php
<?php
echo "=== WITHOUT strict_types ===\n\n";
function addWeak($a, $b) {
return $a + $b;
}
echo "addWeak(5, 3): " . addWeak(5, 3) . "\n";
echo "addWeak(5, '3'): " . addWeak(5, "3") . "\n";
echo "addWeak('5', '3'): " . addWeak("5", "3") . "\n";
echo "addWeak(5, '3 apples'): " . addWeak(5, "3 apples") . " ⚠️\n\n";
echo "=== WITH strict_types ===\n\n";
// In a separate file with declare(strict_types=1);
declare(strict_types=1);
function addStrict(int $a, int $b): int {
return $a + $b;
}
try {
echo "addStrict(5, 3): " . addStrict(5, 3) . "\n";
} catch (TypeError $e) {
echo "Error: {$e->getMessage()}\n";
}
try {
echo "addStrict(5, '3'): ";
echo addStrict(5, "3"); // This will throw TypeError
} catch (TypeError $e) {
echo "TypeError - Caught as expected! ✅\n";
}
echo "\nConclusion: Always use strict_types!\n";

Learn PHP’s compound types: arrays, objects, callables, and iterables.

PHP arrays are incredibly flexible—they’re arrays, lists, and maps all in one:

::: code-group

<?php
declare(strict_types=1);
// Indexed array (like Java ArrayList)
$fruits = ["apple", "banana", "cherry"];
echo $fruits[0]; // "apple"
// Associative array (like Java HashMap)
$user = [
"name" => "Alice",
"age" => 30,
"email" => "alice@example.com"
];
echo $user["name"]; // "Alice"
// Mixed array (no Java equivalent)
$mixed = [1, "two", 3.0, true];
// Multidimensional array
$matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
echo $matrix[1][2]; // 6
// Array functions (similar to Java streams)
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(fn($n) => $n * 2, $numbers); // [2, 4, 6, 8, 10]
$evens = array_filter($numbers, fn($n) => $n % 2 === 0); // [2, 4]
$sum = array_reduce($numbers, fn($acc, $n) => $acc + $n, 0); // 15
// ArrayList (indexed)
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
System.out.println(fruits.get(0)); // "apple"
// HashMap (key-value)
Map<String, Object> user = new HashMap<>();
user.put("name", "Alice");
user.put("age", 30);
user.put("email", "alice@example.com");
System.out.println(user.get("name")); // "Alice"
// No direct equivalent to PHP's mixed array
// Need separate types or Object[]
// 2D array
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println(matrix[1][2]); // 6
// Streams (Java 8+)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubled = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
int sum = numbers.stream()
.reduce(0, Integer::sum);

:::

Array Functions vs Java Streams Comparison

Section titled “Array Functions vs Java Streams Comparison”

For Java developers familiar with Stream API, here’s how PHP array functions map:

Java Stream OperationPHP Array FunctionExample
.map(x -> x * 2)array_map(fn($x) => $x * 2, $arr)Transform each element
.filter(x -> x > 0)array_filter($arr, fn($x) => $x > 0)Keep matching elements
.reduce(0, (a,b) -> a+b)array_reduce($arr, fn($a,$b) => $a+$b, 0)Combine into single value
.forEach(x -> print(x))array_walk($arr, fn($x) => print($x))Side effects on each element
.anyMatch(x -> x > 0)array_any($arr, fn($x) => $x > 0)Check if any match (PHP 8.4+)
.allMatch(x -> x > 0)array_all($arr, fn($x) => $x > 0)Check if all match (PHP 8.4+)
.findFirst()array_find($arr, fn($x) => condition)Find first matching (PHP 8.4+)
.collect(toList())Return value (already array)No collection needed
.count()count($arr)Get size
.sorted()sort($arr) or usort()Sort elements
.distinct()array_unique($arr)Remove duplicates
.skip(n)array_slice($arr, $n)Skip first n elements
.limit(n)array_slice($arr, 0, $n)Take first n elements
.flatMap()array_merge(...array_map())Flatten and map

Example comparison:

::: code-group

<?php
declare(strict_types=1);
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Chain operations
$result = array_reduce(
array_map(
fn($n) => $n ** 2,
array_filter($numbers, fn($n) => $n % 2 === 0)
),
fn($acc, $n) => $acc + $n,
0
);
echo "Sum of squares of evens: $result\n"; // 220
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int result = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.reduce(0, Integer::sum);
System.out.println("Sum of squares of evens: " + result); // 220

:::

<?php
declare(strict_types=1);
// Array type hint (no specific element type)
function processItems(array $items): array
{
return array_map('strtoupper', $items);
}
// Better: Use PHPDoc for specific types (like Java generics)
/**
* @param array<int, string> $names Array of strings
* @return array<int, string>
*/
function uppercaseNames(array $names): array
{
return array_map('strtoupper', $names);
}
/**
* @param array<string, mixed> $data Associative array
* @return array<string, mixed>
*/
function validateData(array $data): array
{
// Validation logic
return $data;
}

::: tip PHPDoc Annotations PHP doesn’t have generics like Java, but PHPDoc annotations provide type information to IDEs and static analyzers:

  • @param array<int, string> = List<String> in Java
  • @param array<string, int> = Map<String, Integer> in Java
  • @return array<string, mixed> = Map<String, Object> in Java :::
<?php
declare(strict_types=1);
class User
{
public function __construct(
public string $name,
public int $age
) {}
}
// Object type hint
function greetUser(object $user): string
{
// Works with any object
return "Hello!";
}
// Specific class type hint (like Java)
function processUser(User $user): void
{
echo "Processing {$user->name}";
}
// Callable type (like Java's functional interfaces)
function execute(callable $callback, int $value): int
{
return $callback($value);
}
// Usage
$double = fn($n) => $n * 2;
echo execute($double, 5); // 10
// Method reference (like Java::method)
function process(array $data, callable $processor): array
{
return array_map($processor, $data);
}
echo process([1, 2, 3], fn($n) => $n * 2); // [2, 4, 6]

Master PHP operators and understand differences from Java.

::: code-group

<?php
declare(strict_types=1);
$a = 10;
$b = 3;
echo $a + $b; // 13 (addition)
echo $a - $b; // 7 (subtraction)
echo $a * $b; // 30 (multiplication)
echo $a / $b; // 3.333... (division, always returns float!)
echo $a % $b; // 1 (modulus)
echo $a ** $b; // 1000 (exponentiation, PHP 5.6+)
// Integer division (PHP 7.0+)
echo intdiv($a, $b); // 3
// Increment/decrement
$c = 5;
echo ++$c; // 6 (pre-increment)
echo $c++; // 6 (post-increment, then $c = 7)
echo --$c; // 6 (pre-decrement)
echo $c--; // 6 (post-decrement, then $c = 5)
int a = 10;
int b = 3;
System.out.println(a + b); // 13
System.out.println(a - b); // 7
System.out.println(a * b); // 30
System.out.println(a / b); // 3 (integer division!)
System.out.println(a % b); // 1
System.out.println(Math.pow(a, b)); // 1000.0
// For float division in Java:
System.out.println((double)a / b); // 3.333...
// Increment/decrement (same as PHP)
int c = 5;
System.out.println(++c); // 6
System.out.println(c++); // 6
System.out.println(--c); // 6
System.out.println(c--); // 6

:::

::: warning Division Behavior Key Difference:

  • Java: 10 / 3 = 3 (integer division)
  • PHP: 10 / 3 = 3.333... (always float division)
  • PHP: Use intdiv(10, 3) for integer division (returns 3) :::
<?php
declare(strict_types=1);
// Equality comparisons
var_dump(5 == 5); // true (equal, with type juggling)
var_dump(5 === 5); // true (identical, same type and value)
var_dump(5 == "5"); // true ⚠️ (type juggling)
var_dump(5 === "5"); // false ✅ (different types)
// Inequality
var_dump(5 != 6); // true (not equal, with type juggling)
var_dump(5 !== "5"); // true ✅ (not identical)
// Relational
var_dump(5 < 10); // true
var_dump(5 > 10); // false
var_dump(5 <= 5); // true
var_dump(5 >= 10); // false
// Spaceship operator (PHP 7+, no Java equivalent)
echo 5 <=> 10; // -1 (5 is less than 10)
echo 10 <=> 10; // 0 (equal)
echo 15 <=> 10; // 1 (15 is greater than 10)
// Null coalescing (PHP 7+, like Java's Optional.orElse())
$name = $user["name"] ?? "Guest"; // Use "Guest" if $user["name"] is null/undefined
$age = $data["age"] ?? $default ?? 0; // Chain multiple ??

::: tip Comparison Best Practices For Java developers:

  1. Always use === and !== (like Java’s == and !=)
  2. Never use == or != unless you specifically need type juggling
  3. Use spaceship operator <=> for comparisons (returns -1, 0, or 1)
  4. Use null coalescing ?? instead of ternary for default values :::
<?php
declare(strict_types=1);
// Concatenation (dot operator, NOT plus!)
$firstName = "Alice";
$lastName = "Smith";
$fullName = $firstName . " " . $lastName; // "Alice Smith"
// Concatenation assignment
$message = "Hello";
$message .= ", World!"; // "Hello, World!" (like += in Java for strings)
// String comparison
var_dump("apple" < "banana"); // true (lexicographic)
var_dump("a" <=> "b"); // -1

Java equivalent:

String firstName = "Alice";
String lastName = "Smith";
String fullName = firstName + " " + lastName;
String message = "Hello";
message += ", World!";
System.out.println("apple".compareTo("banana")); // Negative number
<?php
declare(strict_types=1);
$a = true;
$b = false;
// Logical AND
var_dump($a && $b); // false (short-circuit, like Java)
var_dump($a and $b); // false (lower precedence)
// Logical OR
var_dump($a || $b); // true (short-circuit)
var_dump($a or $b); // true (lower precedence)
// Logical NOT
var_dump(!$a); // false
var_dump(!$b); // true
// Logical XOR
var_dump($a xor $b); // true (true if exactly one is true)

::: warning PHP Has Two Sets of Logical Operators

  • &&, ||, ! - Higher precedence (like Java)
  • and, or, not - Lower precedence (avoid these, use symbolic versions)

Recommendation: Always use &&, ||, ! for consistency with Java. :::

Bitwise operators work on the binary representation of integers:

::: code-group

<?php
declare(strict_types=1);
$a = 5; // 0101 in binary
$b = 3; // 0011 in binary
// Bitwise AND
echo $a & $b; // 1 (0001) - bits set in both
// Bitwise OR
echo $a | $b; // 7 (0111) - bits set in either
// Bitwise XOR (exclusive OR)
echo $a ^ $b; // 6 (0110) - bits set in one but not both
// Bitwise NOT (complement)
echo ~$a; // -6 (inverts all bits, result depends on integer size)
// Left shift (multiply by 2^n)
echo $a << 1; // 10 (01010) - shift left by 1 bit
// Right shift (divide by 2^n)
echo $a >> 1; // 2 (0010) - shift right by 1 bit
// Practical example: Flags/permissions
const READ = 1; // 0001
const WRITE = 2; // 0010
const EXECUTE = 4; // 0100
$permissions = READ | WRITE; // 0011 (read and write)
if ($permissions & READ) {
echo "Has read permission";
}
int a = 5; // 0101 in binary
int b = 3; // 0011 in binary
// Bitwise AND
System.out.println(a & b); // 1 (0001)
// Bitwise OR
System.out.println(a | b); // 7 (0111)
// Bitwise XOR
System.out.println(a ^ b); // 6 (0110)
// Bitwise NOT
System.out.println(~a); // -6 (inverts all bits)
// Left shift
System.out.println(a << 1); // 10 (01010)
// Right shift (signed)
System.out.println(a >> 1); // 2 (0010)
// Unsigned right shift (Java-specific)
System.out.println(a >>> 1); // 2 (for positive numbers, same as >>)
// Flags example
final int READ = 1; // 0001
final int WRITE = 2; // 0010
final int EXECUTE = 4; // 0100
int permissions = READ | WRITE; // 0011
if ((permissions & READ) != 0) {
System.out.println("Has read permission");
}

:::

PHP supports bitwise assignment operators (same as Java):

<?php
declare(strict_types=1);
$a = 5;
$a &= 3; // $a = $a & 3 (bitwise AND assignment)
$a |= 3; // $a = $a | 3 (bitwise OR assignment)
$a ^= 3; // $a = $a ^ 3 (bitwise XOR assignment)
$a <<= 1; // $a = $a << 1 (left shift assignment)
$a >>= 1; // $a = $a >> 1 (right shift assignment)
<?php
declare(strict_types=1);
// 1. Flags and permissions
class Permissions {
const READ = 1; // 0001
const WRITE = 2; // 0010
const EXECUTE = 4; // 0100
const DELETE = 8; // 1000
public static function hasPermission(int $userPerms, int $required): bool {
return ($userPerms & $required) === $required;
}
public static function addPermission(int $perms, int $newPerm): int {
return $perms | $newPerm;
}
public static function removePermission(int $perms, int $removePerm): int {
return $perms & ~$removePerm;
}
}
$userPerms = Permissions::READ | Permissions::WRITE;
var_dump(Permissions::hasPermission($userPerms, Permissions::READ)); // true
var_dump(Permissions::hasPermission($userPerms, Permissions::EXECUTE)); // false
// 2. Checking if number is even/odd
function isEven(int $n): bool {
return ($n & 1) === 0; // Last bit is 0 for even numbers
}
// 3. Fast multiplication/division by powers of 2
$value = 10;
$doubled = $value << 1; // 20 (multiply by 2)
$halved = $value >> 1; // 5 (divide by 2)
$quadrupled = $value << 2; // 40 (multiply by 4)

::: tip When to Use Bitwise Operators

  • Flags/permissions - Efficient way to store multiple boolean flags
  • Performance-critical code - Faster than arithmetic for powers of 2
  • Low-level operations - Working with binary data, protocols
  • Generally avoid - Use only when you have a specific need :::

Like Java, PHP has operator precedence rules (higher precedence = evaluated first):

PrecedenceOperatorDescriptionExample
Highest()Parentheses(5 + 3) * 2
**Exponentiation2 ** 3 = 8
++, --, !, ~Increment, decrement, not, bitwise NOT!$flag, $i++, ~$a
*, /, %Multiplication, division, modulo5 * 3 / 2
+, -, .Addition, subtraction, concatenation5 + 3, "a" . "b"
<, <=, >, >=Comparison5 < 10
==, ===, !=, !==Equality5 === 5
&Bitwise AND$a & $b
^Bitwise XOR$a ^ $b
|Bitwise OR$a | $b
&&Logical AND$a && $b
||Logical OR$a || $b
??Null coalescing$a ?? $b
? :Ternary$x ? $y : $z
=, +=, -=, &=, |=, etc.Assignment$a = 5
Lowestand, or, xorLow-precedence logical$a and $b

Important differences from Java:

<?php
declare(strict_types=1);
// String concatenation has higher precedence than addition
echo "Total: " . 5 + 3; // ⚠️ "Total: 5" + 3 = 3 (confusing!)
echo "Total: " . (5 + 3); // ✅ "Total: 8"
// Null coalescing vs ternary
$value = $data['key'] ?? 'default'; // Checks if key exists and not null
$value = $data['key'] ?: 'default'; // Checks if value is truthy (avoid!)
// Logical operator precedence trap
$result = true or false && false; // true (because 'or' is low precedence)
$result = true || false && false; // true (&& evaluated first)
echo $result = 5 + 3; // ⚠️ Assignment has low precedence, echoes 8
echo ($result = 5 + 3); // ✅ Explicit with parentheses

::: tip Best Practice Always use parentheses when mixing operators to make precedence explicit:

// Unclear
$result = $a + $b * $c;
// Clear
$result = $a + ($b * $c);
// Unclear
if ($isValid && $isActive || $isAdmin)
// Clear
if (($isValid && $isActive) || $isAdmin)

:::


Learn how to explicitly convert types in PHP.

::: code-group

<?php
declare(strict_types=1);
// Cast to int
$floatValue = 10.7;
$intValue = (int)$floatValue; // 10 (truncates)
$intValue2 = intval($floatValue); // 10
// Cast to float
$intValue = 10;
$floatValue = (float)$intValue; // 10.0
$floatValue2 = floatval($intValue); // 10.0
// Cast to string
$number = 123;
$text = (string)$number; // "123"
$text2 = strval($number); // "123"
// Cast to bool
$value = 1;
$bool = (bool)$value; // true
$bool2 = boolval($value); // true
// Cast to array
$object = (object)["name" => "Alice"];
$array = (array)$object; // ["name" => "Alice"]
// Type checking
var_dump(is_int(10)); // true
var_dump(is_float(10.5)); // true
var_dump(is_string("hello")); // true
var_dump(is_bool(true)); // true
var_dump(is_array([1, 2])); // true
var_dump(is_object(new stdClass())); // true
var_dump(is_null(null)); // true
// Cast to int
double floatValue = 10.7;
int intValue = (int)floatValue; // 10 (truncates)
// Cast to double
int intValue = 10;
double doubleValue = (double)intValue; // 10.0
// Convert to String
int number = 123;
String text = String.valueOf(number); // "123"
String text2 = Integer.toString(number);
// Parse from String
int parsed = Integer.parseInt("123");
double parsedFloat = Double.parseDouble("10.5");
// Type checking with instanceof
Object obj = "Hello";
if (obj instanceof String) {
String str = (String)obj;
}

:::

Type Juggling Rules (Without Strict Types)

Section titled “Type Juggling Rules (Without Strict Types)”
type-casting-example.php
<?php
// String to number
$str = "123";
$num = $str + 0; // 123 (int)
$num = $str * 1; // 123 (int)
// Truthy/Falsy values (like JavaScript)
// Falsy: false, 0, 0.0, "", "0", null, []
// Everything else is truthy
if ("") echo "truthy"; // Doesn't print (empty string is falsy)
if ("0") echo "truthy"; // Doesn't print ("0" is falsy!)
if (0) echo "truthy"; // Doesn't print
if ([]) echo "truthy"; // Doesn't print (empty array is falsy)
if ("hello") echo "truthy"; // Prints (non-empty string is truthy)
if (1) echo "truthy"; // Prints
if ([1]) echo "truthy"; // Prints (non-empty array is truthy)

::: danger Falsy Value Gotcha In PHP, "0" (string zero) is falsy, unlike most languages:

if ("0") {
// This doesn't execute! ⚠️
}
// Solution: Use strict comparison
if ("0" !== "") {
// This executes ✅
}

:::


Section 7: Nullable Types and Modern Type Features

Section titled “Section 7: Nullable Types and Modern Type Features”

Understand PHP 8+ nullable types, union types, and special types.

::: code-group

<?php
declare(strict_types=1);
// Nullable type with ?
function findUser(int $id): ?User
{
// Returns User or null
$user = $this->repository->find($id);
return $user; // Can be null
}
// Using nullable parameters
function greet(?string $name): string
{
if ($name === null) {
return "Hello, Guest!";
}
return "Hello, $name!";
}
// Nullable with null coalescing
$user = findUser(123);
$userName = $user?->getName() ?? "Unknown"; // Null-safe operator
// Old style (still valid)
function oldStyleNullable($value = null): void
{
// $value can be any type or null
}
// Java Optional pattern
public Optional<User> findUser(int id) {
User user = repository.find(id);
return Optional.ofNullable(user);
}
// Using Optional
Optional<User> userOpt = findUser(123);
String userName = userOpt
.map(User::getName)
.orElse("Unknown");
// Java 8+ method reference
public String greet(String name) {
return Optional.ofNullable(name)
.map(n -> "Hello, " + n + "!")
.orElse("Hello, Guest!");
}

:::

::: tip Null-safe Operator (PHP 8.0+) The ?-> operator is like Java’s Optional.map():

// PHP
$email = $user?->getProfile()?->getEmail() ?? 'no-email@example.com';
// Java equivalent
String email = Optional.ofNullable(user)
.flatMap(u -> Optional.ofNullable(u.getProfile()))
.map(Profile::getEmail)
.orElse("no-email@example.com");

:::

Union types allow a value to be one of several types:

::: code-group

<?php
declare(strict_types=1);
// Union type: int OR float
function processNumber(int|float $value): int|float
{
return $value * 2;
}
echo processNumber(5); // 10
echo processNumber(5.5); // 11.0
// Union with null (alternative to ?)
function findUser(int $id): User|null
{
// Same as: ?User
return $this->repository->find($id);
}
// Multiple types in union
function formatValue(int|float|string|bool $value): string
{
return match (true) {
is_int($value) => "Integer: $value",
is_float($value) => "Float: $value",
is_string($value) => "String: $value",
is_bool($value) => "Boolean: " . ($value ? 'true' : 'false'),
};
}
// Union types in properties (PHP 8.0+)
class Payment
{
public function __construct(
public int|float $amount,
public string|null $currency = null
) {}
}
// Java doesn't have union types
// You'd use method overloading or common interface
public String formatValue(int value) {
return "Integer: " + value;
}
public String formatValue(double value) {
return "Float: " + value;
}
public String formatValue(String value) {
return "String: " + value;
}
public String formatValue(boolean value) {
return "Boolean: " + value;
}
// Or use generics with bounded types
public <T extends Number> T processNumber(T value) {
// Limited compared to PHP union types
return value;
}

:::

The mixed type accepts any type (like Java’s Object):

<?php
declare(strict_types=1);
// Mixed type - accepts anything
function processData(mixed $data): mixed
{
return match (true) {
is_array($data) => count($data),
is_string($data) => strlen($data),
is_int($data) => $data * 2,
default => null
};
}
echo processData([1, 2, 3]); // 3
echo processData("hello"); // 5
echo processData(10); // 20

Java equivalent:

public Object processData(Object data) {
if (data instanceof int[]) {
return ((int[])data).length;
} else if (data instanceof String) {
return ((String)data).length();
} else if (data instanceof Integer) {
return (Integer)data * 2;
}
return null;
}

Intersection types require a value to implement multiple interfaces:

<?php
declare(strict_types=1);
interface Loggable {
public function log(): void;
}
interface Cacheable {
public function cache(): void;
}
// Must implement BOTH interfaces
function process(Loggable&Cacheable $object): void
{
$object->log();
$object->cache();
}
class User implements Loggable, Cacheable
{
public function log(): void { /* ... */ }
public function cache(): void { /* ... */ }
}
$user = new User();
process($user); // ✅ Implements both interfaces

Java equivalent:

// Java uses extends for multiple interfaces
public <T extends Loggable & Cacheable> void process(T object) {
object.log();
object.cache();
}

The never type indicates a function never returns (like Java’s void for exceptions):

<?php
declare(strict_types=1);
// Never returns normally (always throws or exits)
function fail(string $message): never
{
throw new Exception($message);
}
function redirect(string $url): never
{
header("Location: $url");
exit;
}
// Usage
function processUser(int $id): User
{
$user = findUser($id);
if ($user === null) {
fail("User not found"); // Never returns
}
return $user; // Type checker knows $user is not null here
}

Like Java’s void, indicates no return value:

<?php
declare(strict_types=1);
function logMessage(string $message): void
{
echo $message . "\n";
// No return statement, or return with no value
}
function saveUser(User $user): void
{
$this->repository->save($user);
return; // Optional
}
PHP TypeJava EquivalentDescription
intintInteger number
floatdoubleFloating point
stringStringText data
boolbooleanTrue/false
arrayList, MapArray/associative array
objectObjectAny object
callableFunctional interfaceFunction reference
iterableIterableArray or Traversable
mixedObjectAny type (PHP 8.0+)
neverVoid (for exceptions)Never returns (PHP 8.1+)
voidvoidNo return value
?TypeOptional<Type>Nullable type
Type1|Type2No equivalentUnion type (PHP 8.0+)
Type1&Type2<T extends A & B>Intersection (PHP 8.1+)
nullnullNull value

Create a practical validation class using strict types.

validator.php
<?php
declare(strict_types=1);
class Validator
{
/**
* Validate that a value is a non-empty string
*/
public function isNonEmptyString(mixed $value): bool
{
return is_string($value) && $value !== "";
}
/**
* Validate that a value is a positive integer
*/
public function isPositiveInt(mixed $value): bool
{
return is_int($value) && $value > 0;
}
/**
* Validate that a value is within a range
*/
public function isInRange(int|float $value, int|float $min, int|float $max): bool
{
return $value >= $min && $value <= $max;
}
/**
* Validate email format
*/
public function isValidEmail(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* Validate array has required keys
*
* @param array<string, mixed> $data
* @param array<int, string> $requiredKeys
*/
public function hasRequiredKeys(array $data, array $requiredKeys): bool
{
foreach ($requiredKeys as $key) {
if (!array_key_exists($key, $data)) {
return false;
}
}
return true;
}
/**
* Validate and sanitize user data
*
* @param array<string, mixed> $data
* @return array{valid: bool, errors: array<string, string>, data: array<string, mixed>}
*/
public function validateUser(array $data): array
{
$errors = [];
// Check required fields
if (!$this->hasRequiredKeys($data, ['name', 'email', 'age'])) {
$errors['missing'] = 'Missing required fields';
return ['valid' => false, 'errors' => $errors, 'data' => []];
}
// Validate name
if (!$this->isNonEmptyString($data['name'])) {
$errors['name'] = 'Name must be a non-empty string';
}
// Validate email
if (!$this->isValidEmail($data['email'])) {
$errors['email'] = 'Invalid email format';
}
// Validate age
if (!$this->isPositiveInt($data['age'])) {
$errors['age'] = 'Age must be a positive integer';
} elseif (!$this->isInRange($data['age'], 1, 120)) {
$errors['age'] = 'Age must be between 1 and 120';
}
$valid = empty($errors);
return [
'valid' => $valid,
'errors' => $errors,
'data' => $valid ? $data : []
];
}
}
// Usage
$validator = new Validator();
$userData = [
'name' => 'Alice',
'email' => 'alice@example.com',
'age' => 30
];
$result = $validator->validateUser($userData);
if ($result['valid']) {
echo "User data is valid!\n";
print_r($result['data']);
} else {
echo "Validation errors:\n";
print_r($result['errors']);
}
// Test with invalid data
$invalidData = [
'name' => '',
'email' => 'invalid-email',
'age' => -5
];
$invalidResult = $validator->validateUser($invalidData);
print_r($invalidResult['errors']);
User data is valid!
Array
(
[name] => Alice
[email] => alice@example.com
[age] => 30
)
Array
(
[name] => Name must be a non-empty string
[email] => Invalid email format
[age] => Age must be a positive integer
)

::: tip PHP 8+ Features Used

  • mixed type (accepts any type)
  • Union types: int|float
  • Typed properties
  • Return type declarations
  • Array shape documentation in PHPDoc :::

Create a class that converts temperatures between Celsius and Fahrenheit.

Requirements:

  • Use strict types
  • Type hint all parameters and return values
  • Handle invalid inputs (throw exceptions)
  • Write tests for edge cases

Starter code:

<?php
declare(strict_types=1);
class TemperatureConverter
{
// TODO: Implement celsiusToFahrenheit
// Formula: F = C * 9/5 + 32
// TODO: Implement fahrenheitToCelsius
// Formula: C = (F - 32) * 5/9
}
Solution
<?php
declare(strict_types=1);
class TemperatureConverter
{
public function celsiusToFahrenheit(float $celsius): float
{
return ($celsius * 9/5) + 32;
}
public function fahrenheitToCelsius(float $fahrenheit): float
{
return ($fahrenheit - 32) * 5/9;
}
public function isAbsoluteZero(float $celsius): bool
{
return $celsius <= -273.15;
}
}
// Test
$converter = new TemperatureConverter();
echo $converter->celsiusToFahrenheit(0) . "°F\n"; // 32°F
echo $converter->celsiusToFahrenheit(100) . "°F\n"; // 212°F
echo $converter->fahrenheitToCelsius(32) . "°C\n"; // 0°C
echo $converter->fahrenheitToCelsius(212) . "°C\n"; // 100°C
var_dump($converter->isAbsoluteZero(-300)); // true

Exercise 2: Type Juggling vs Strict Comparison

Section titled “Exercise 2: Type Juggling vs Strict Comparison”

Demonstrate the differences between loose and strict comparisons.

Task: Create a script that shows surprising behavior with == and expected behavior with ===.

Solution
<?php
declare(strict_types=1);
function compareValues($a, $b): void
{
echo "Comparing: ";
var_export($a);
echo " and ";
var_export($b);
echo "\n";
echo " == (loose): ";
var_dump($a == $b);
echo " === (strict): ";
var_dump($a === $b);
echo "\n";
}
echo "Demonstration of type juggling:\n\n";
compareValues(0, ""); // Surprising with ==
compareValues(0, "0"); // Surprising with ==
compareValues(1, "1");
compareValues(1, true); // Surprising with ==
compareValues(false, ""); // Surprising with ==
compareValues(false, null); // Surprising with ==
compareValues("10", 10);
compareValues([], false); // Surprising with ==
echo "Recommendation: Always use === for comparisons!\n";

Create a utility class that transforms arrays similar to Java streams.

Requirements:

  • Method to map (transform each element)
  • Method to filter (keep elements matching condition)
  • Method to reduce (combine elements into single value)
  • Use type hints and PHPDoc
Solution
<?php
declare(strict_types=1);
class ArrayTransformer
{
/**
* @param array<int, mixed> $array
* @param callable(mixed): mixed $callback
* @return array<int, mixed>
*/
public function map(array $array, callable $callback): array
{
return array_map($callback, $array);
}
/**
* @param array<int, mixed> $array
* @param callable(mixed): bool $callback
* @return array<int, mixed>
*/
public function filter(array $array, callable $callback): array
{
return array_filter($array, $callback);
}
/**
* @param array<int, mixed> $array
* @param callable(mixed, mixed): mixed $callback
* @param mixed $initial
* @return mixed
*/
public function reduce(array $array, callable $callback, mixed $initial = null): mixed
{
return array_reduce($array, $callback, $initial);
}
/**
* Fluent interface example
*
* @param array<int, int> $numbers
* @return int
*/
public function sumOfSquaresOfEvens(array $numbers): int
{
$evens = $this->filter($numbers, fn($n) => $n % 2 === 0);
$squares = $this->map($evens, fn($n) => $n ** 2);
return $this->reduce($squares, fn($acc, $n) => $acc + $n, 0);
}
}
// Test
$transformer = new ArrayTransformer();
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Map: double each number
$doubled = $transformer->map($numbers, fn($n) => $n * 2);
print_r($doubled); // [2, 4, 6, ..., 20]
// Filter: get even numbers
$evens = $transformer->filter($numbers, fn($n) => $n % 2 === 0);
print_r($evens); // [2, 4, 6, 8, 10]
// Reduce: sum all numbers
$sum = $transformer->reduce($numbers, fn($acc, $n) => $acc + $n, 0);
echo "Sum: $sum\n"; // 55
// Chain operations
$result = $transformer->sumOfSquaresOfEvens($numbers);
echo "Sum of squares of evens: $result\n"; // 220 (4 + 16 + 36 + 64 + 100)

Before moving to the next chapter, ensure you can:

  • Explain the difference between weak and strict typing in PHP
  • Use declare(strict_types=1) in all your PHP files
  • Apply type hints to function parameters and return values
  • Understand PHP’s scalar types: int, float, string, bool
  • Work with arrays as both indexed and associative
  • Use === for comparisons (never ==)
  • Cast between types explicitly when needed
  • Leverage PHPDoc annotations for complex types
  • Understand string interpolation with double quotes
  • Use the null coalescing operator ??
  • Use nullable types (?Type) and understand null-safe operator (?->)
  • Apply union types (Type1|Type2) in PHP 8.0+
  • Understand operator precedence and use parentheses for clarity
  • Know the type coercion rules and avoid them with strict types
  • Map Java Stream operations to PHP array functions
  • Define constants using define() and const
  • Understand variable scope (global, local, static)
  • Use the global keyword and $GLOBALS superglobal appropriately
  • Work with references (&$var) and understand pass-by-reference
  • Apply bitwise operators (&, |, ^, ~, <<, >>) when needed
  • Understand variable variables ($$var) and when to avoid them

::: tip Ready for More? In Chapter 2: Control Flow & Functions, we’ll dive into control structures, functions, and closures, comparing them to Java equivalents. :::


PHP Documentation:

For Java Developers: