Cheatsheet PHP
Linguagem de programação web server-side
PHP
Basic
Opening and Closing PHP
<?php // PHP code here echo "Hello World"; ?> <!-- Short tag (echo) --> <?= "Direct output" ?> <!-- In pure .php files, omit the ?> --> <?php echo "No closing tag needed";
Use <?php to open and ?> to close. The short tag <?= is equivalent to echo. In 100% PHP files, omit the final ?> to avoid accidental output.
Concatenation and Interpolation
$name = "Anna";
$age = 25;
// Concatenation with dot
echo "Hello " . $name . "!";
// Interpolation (double quotes)
echo "Hello $name, you are $age years";
echo "Hello {$name}!"; // with braces
// Single quotes (literal)
echo 'Hello $name'; // shows: Hello $name
// Heredoc (multi-line)
$text = <<<TXT
Hello $name,
You are $age years.
TXT;
// Nowdoc (no interpolation)
$raw = <<<'TXT'
No $interpolation here
TXT;The . operator concatenates strings. Double quotes "$var" interpolate variables. {$var} for complex expressions. Heredoc allows multi-line with interpolation; Nowdoc without.
Ternary and Elvis Operator
// Full ternary
$status = ($age >= 18) ? "adult" : "minor";
// Elvis (if truthy, use the value)
$name = $input ?: "Anonymous";
// Equivalent to: $input ? $input : "Anonymous"
// Null coalescing (if non-null)
$name = $_GET['name'] ?? "Visitor";
// Chaining
$level = $score > 90 ? "A"
: ($score > 70 ? "B" : "C");
// Ternary in HTML
<?= $active ? "Active" : "Inactive" ?>The ternary ? : is a compact if/else. The Elvis operator ?: returns the value if truthy. ?? only checks null. Use sparingly — long ternaries are hard to read.
Magic Constants
__LINE__; // current line number __FILE__; // full path of the file __DIR__; // folder of the file __FUNCTION__; // function name __CLASS__; // class name __METHOD__; // Class::method __NAMESPACE__; // current namespace // Practical use require __DIR__ . '/config.php'; echo "Error in " . __FILE__ . ":" . __LINE__; // PHP 8.1: enum __TRAIT__; // trait name
Magic constants start and end with __. __DIR__ replaces dirname(__FILE__). __LINE__ gives the current line. Useful for debugging, includes and logging.
Variables
$name = "Cláudio";
$age = 30;
$price = 19.99;
$active = true;
$null = null;
// Dynamic variables
$field = "name";
echo $$field; // "Cláudio"
// Constants
define("MAX", 100);
const PI = 3.14159;
// Check existence
isset($name); // true
isset($inexist); // falseVariables start with $. define() and const create constants. isset() checks if it exists and is not null. Types: string, int, float, bool, null.
Superglobals
$_GET['id']; // URL parameters $_POST['name']; // form data $_REQUEST['x']; // GET + POST + COOKIE $_SESSION['user']; // session $_COOKIE['token']; // cookies $_SERVER['HTTP_HOST']; // server $_FILES['upload']; // upload $_ENV['APP_KEY']; // environment // Practical example $id = $_GET['id'] ?? 0; $name = $_POST['name'] ?? '';
Superglobals are automatic arrays available everywhere. $_GET for URL, $_POST for forms, $_SESSION for session. Always sanitize before using!
Increment and Assignment
$x = 5; $x++; // 6 (post-increment) ++$x; // 7 (pre-increment) $x--; // 6 (post-decrement) // Compound assignment $x += 10; // $x = $x + 10 $x -= 3; // $x = $x - 3 $x *= 2; // $x = $x * 2 $x /= 4; // $x = $x / 4 $x %= 3; // $x = $x % 3 $x .= "!"; // $x = $x . "!" (string) $x ??= "default"; // only if null
++$x increments before using; $x++ uses then increments. .= concatenates strings. ??= assigns only if the variable is null. +=, -=, *= are arithmetic shortcuts.
Data Types
// Scalar types $str = "text"; // string $num = 42; // int $float = 3.14; // float $bool = true; // bool // Compound types $arr = [1, 2, 3]; // array $obj = new stdClass; // object // Check type gettype($num); // "integer" is_string($str); // true is_int($num); // true is_array($arr); // true // Casting $str2 = (string) 42; // "42" $int2 = (int) "42px"; // 42 $bool2 = (bool) ""; // false
Use gettype() to see the type and is_*() to check. (int), (string), (bool) do casting. Falsy values: 0, "", null, false, [].
Include and Require
// Includes file (warning if it does not exist) include 'header.php'; // Required (fatal error if it does not exist) require 'config.php'; // Includes only once require_once 'database.php'; include_once 'helpers.php'; // Path relative to the current file require __DIR__ . '/partials/nav.php'; // Return values from files $config = require 'config.php';
require for essential files (stops if it fails). include for optional ones. *_once avoids duplicate inclusion. __DIR__ gives the absolute path of the current folder.
Echo, Print and Output
// echo (faster, no return)
echo "Hello", " ", "World"; // multiple args
echo "Hello" . " World"; // concatenation
// print (returns 1)
print "Hello World";
// printf (formatted)
printf("%s tem %d years", "Anna", 25);
// Output buffering
ob_start();
echo "Content capturado";
$html = ob_get_clean();
// print_r for debugging
print_r($array);echo is faster and accepts multiple arguments. print returns 1 (usable in expressions). printf() formats with placeholders. ob_start()/ob_get_clean() capture output into a variable.
Operators
// Arithmetic $a + $b; // addition $a - $b; // subtraction $a * $b; // multiplication $a / $b; // division $a % $b; // remainder $a ** $b; // power (2**3 = 8) // Comparison $a == $b; // equal (value) $a === $b; // identical (value + type) $a != $b; // different $a <=> $b; // spaceship (-1, 0, 1) // Logical $a && $b; // AND $a || $b; // OR !$a; // NOT // Null coalescing $val = $_GET['x'] ?? 'default';
=== compares value AND type (always prefer it). <=> (spaceship) returns -1, 0 or 1. ?? returns the first non-null value. ** is power. % is the division remainder.
Comments
// Single-line comment
# Also works (shell style)
/*
* Multi-line
* comment
*/
/**
* DocBlock for functions/classes
* @param string $name
* @return string
*/
function greet(string $name): string {
return "Hello $name";
}// and # for one line. /* */ for multi-line. /** */ (DocBlock) for documentation — IDEs read @param, @return, @throws.
Type Conversion (Casting)
// Explicit casting $str = (string) 42; // "42" $int = (int) "42px"; // 42 $float = (float) "3.14"; // 3.14 $bool = (bool) ""; // false $arr = (array) $obj; // object → array // settype (changes the variable) $var = "42"; settype($var, "integer"); // $var is now int // Automatic conversion (type juggling) "10" + 5; // 15 (int) "10" . 5; // "105" (string) true + true; // 2 // PHP 8: stricter // "abc" + 1; // TypeError!
Use (int), (string), (float), (bool) for casting. settype() changes the variable's type. PHP 8 is stricter: operations with non-numeric strings throw TypeError.
Strings
Basic String Functions
$str = " Hello World ";
strlen($str); // 13 (length)
trim($str); // "Hello World"
strtoupper($str); // " HELLO WORLD "
strtolower($str); // " hello world "
ucfirst("hello world"); // "Hello world"
ucwords("hello world"); // "Hello World"
str_repeat("ha", 3); // "hahaha"
str_pad("5", 3, "0", STR_PAD_LEFT); // "005"
// Count occurrences
substr_count("banana", "na"); // 2trim() removes spaces at the ends. strlen() gives the length. strtoupper()/strtolower() change capitalization. str_pad() pads to a size. substr_count() counts repetitions.
String Formatting
// sprintf (formatting)
sprintf("Hello %s, you are %d years old", "Anna", 25);
sprintf("Price: %.2f€", 19.9); // "19.90€"
sprintf("ID: %05d", 42); // "ID: 00042"
sprintf("%x", 255); // "ff" (hex)
// number_format
number_format(1234567.891, 2, ",", ".");
// "1.234.567,89"
// money_format alternative
"R$ " . number_format(99.9, 2);
// nl2br (line breaks → <br>)
echo nl2br("Line 1\nLine 2");
// htmlspecialchars (escape HTML)
htmlspecialchars('<script>alert("x")</script>');sprintf() formats with placeholders: %s (string), %d (int), %f (float). number_format() formats numbers with separators. htmlspecialchars() escapes HTML (prevents XSS).
Serialization
// serialize / unserialize
$data = ['name' => 'Anna', 'items' => [1,2,3]];
$str = serialize($data);
// 'a:2:{s:4:"name";s:3:"Anna";...}'
$restored = unserialize($str);
// Careful: security!
// NEVER unserialize() user data
// Use json_encode/json_decode instead
// __sleep and __wakeup
class User {
public function __sleep(): array {
return ['name', 'email']; // what to serialize
}
public function __wakeup(): void {
// after unserialize
}
}serialize() converts to string. unserialize() restores. Dangerous: never use unserialize() with user data (RCE). Prefer json_encode()/json_decode(). __sleep()/__wakeup() control serialization.
Substring and Position
$str = "Hello World PHP"; // Extract part substr($str, 4); // "World PHP" substr($str, 4, 5); // "World" substr($str, -3); // "PHP" // Find position strpos($str, "World"); // 4 strrpos($str, "P"); // 10 (last) stripos($str, "world"); // 4 (case-insensitive) // Check if it contains str_contains($str, "World"); // true (PHP 8+) str_starts_with($str, "Hello"); // true str_ends_with($str, "PHP"); // true
substr() extracts part of the string. strpos() finds the position (or false). PHP 8+: str_contains(), str_starts_with(), str_ends_with() — more readable than strpos() !== false.
Regex with preg_*
// Test if it matches
preg_match('/^\d{9}$/', $phone);
// Extract first match
preg_match('/(\d{4})-(\d{3})/', $str, $m);
$m[0]; // full match
$m[1]; // 1st group
// All matches
preg_match_all('/\d+/', "a1 b22 c333", $all);
$all[0]; // ["1", "22", "333"]
// Replace with regex
preg_replace('/\s+/', ' ', $text);
preg_replace('/\d+/', 'X', "a1b2"); // "aXbX"
// Split by regex
preg_split('/[\s,]+/', "a, b c");preg_match() tests/finds the first match. preg_match_all() finds all of them. preg_replace() replaces with a pattern. Delimiters: /, #, ~. Modifiers: i (case-insensitive), s (dotall).
mb_* — Multibyte Strings
$str = "Hello World 🌍";
// strlen counts bytes, mb_strlen counts characters
strlen($str); // 16 (bytes)
mb_strlen($str); // 12 (characters)
// Multibyte substring
mb_substr($str, 0, 3); // "Hello"
mb_strtoupper($str); // "HELLO WORLD 🌍"
mb_strtolower($str); // "hello world 🌍"
// Encoding
mb_detect_encoding($str); // "UTF-8"
mb_convert_encoding($str, 'ISO-8859-1');
// Internal encoding
mb_internal_encoding('UTF-8');mb_* functions handle UTF-8 correctly. strlen() counts bytes, mb_strlen() counts characters. Use mb_substr(), mb_strtoupper() for accents and emojis. Set mb_internal_encoding('UTF-8') at the start.
Replacement
// Replace text
str_replace("world", "PHP", "Hello world");
// "Hello PHP"
// Replace with count
str_replace("a", "o", "banana", $count);
// "bonono" ($count = 3)
// Replace with arrays
$search = ["foo", "bar"];
$replace = ["baz", "qux"];
str_replace($search, $replace, "foo bar");
// Replace by position
substr_replace("Hello World", "PHP", 4, 5);
// "Hello PHP"str_replace() replaces all occurrences. The 4th parameter (by reference) counts how many replacements it made. substr_replace() replaces by position. For regex, use preg_replace().
Comparison and Encoding
// Comparison
strcmp("abc", "abc"); // 0 (equal)
strcasecmp("ABC", "abc"); // 0 (case-insensitive)
strncmp("abcdef", "abcxyz", 3); // 0 (3 chars)
// Encoding
mb_strlen("café"); // 4 (correct UTF-8)
mb_strtoupper("café"); // "CAFÉ"
mb_substr("Hello World", 0, 3); // "Hello"
mb_detect_encoding($str);
// Convert encoding
mb_convert_encoding($str, "UTF-8", "ISO-8859-1");
utf8_encode($str); // ISO → UTF-8
utf8_decode($str); // UTF-8 → ISOFor strings with accents/emoji, use mb_* (multibyte) functions. mb_strlen() counts characters correctly. strcmp() compares strings (returns 0 if equal). Always use UTF-8 in your project.
sprintf and printf
// printf: direct output
printf("Name: %s, Age: %d", "Anna", 25);
// sprintf: returns string
$msg = sprintf("Total: %.2f EUR", 19.9);
// "Total: 19.90 EUR"
// Placeholders
sprintf("%05d", 42); // "00042" (padding)
sprintf("%-10s|", "Esq"); // "Esq |" (left)
sprintf("%+d", 42); // "+42" (sign)
sprintf("%x", 255); // "ff" (hex)
sprintf("%b", 10); // "1010" (binary)
// Positional argument
sprintf("%2\$s %1\$s", "World", "Hello");
// "Hello World"sprintf() formats and returns a string. printf() outputs directly. Placeholders: %s string, %d int, %f float, %x hex. Modifiers: %05d padding, %.2f decimals, %-10s alignment.
Split and Join
// Split string into array
$parts = explode(",", "a,b,c");
// ["a", "b", "c"]
$words = explode(" ", "Hello World PHP", 2);
// ["Hello", "World PHP"] (limit 2)
// Join array into string
$str = implode(", ", ["a", "b", "c"]);
// "a, b, c"
$url = implode("/", ["api", "users", "1"]);
// "api/users/1"
// Split into characters
$chars = str_split("ABC");
// ["A", "B", "C"]explode() splits by delimiter. The 3rd parameter limits the number of parts. implode() joins an array with a separator. str_split() splits into individual characters.
Advanced str_replace
// Multiple replacements
$search = ['foo', 'bar', 'baz'];
$replace = ['FOO', 'BAR', 'BAZ'];
str_replace($search, $replace, 'foo bar baz');
// strtr (more efficient for multiple)
strtr('foo bar', ['foo' => 'FOO', 'bar' => 'BAR']);
// Replace only 1st occurrence
$pos = strpos($str, 'needle');
if ($pos !== false) {
$str = substr_replace($str, 'new', $pos, 6);
}
// Remove accents
function removeAccents(string $s): string {
return strtr($s, 'áàãâéêíóôõúüç',
'aaaaeeiooouuc');
}str_replace() accepts arrays for multiple replacements. strtr() is more efficient for key-value pairs. substr_replace() replaces by position. For complex patterns, use preg_replace().
Advanced Trim and Padding
$str = " Hello World ";
trim($str); // "Hello World"
ltrim($str); // "Hello World "
rtrim($str); // " Hello World"
// Trim with custom characters
trim("---text---", "-"); // "text"
trim("00042", "0"); // "42"
trim("\t\n text \r\n"); // "text"
// Padding
str_pad("5", 3, "0", STR_PAD_LEFT); // "005"
str_pad("Hi", 10, "-", STR_PAD_BOTH); // "----Hi----"
str_pad("OK", 6, ".", STR_PAD_RIGHT); // "OK...."
// Wordwrap
wordwrap("Text long here", 10, "\n");trim() removes spaces (or given characters) from the ends. ltrim()/rtrim() only left/right. str_pad() pads to the desired size. wordwrap() wraps text into fixed-size lines.
Arrays
Creating Arrays
// Indexed array
$fruits = ["apple", "banana", "pear"];
// Associative array
$user = [
"name" => "Anna",
"age" => 30,
"active" => true,
];
// Multidimensional array
$matrix = [
[1, 2, 3],
[4, 5, 6],
];
// Create with range
$numbers = range(1, 10);
$letters = range('a', 'f');
// Empty array
$empty = [];Short syntax [] (PHP 5.4+). Arrays can be indexed (0, 1, 2...) or associative (key => value). range() creates sequences. Arrays in PHP are ordered maps (they accept any key type).
array_reduce and array_keys
$numbers = [1, 2, 3, 4, 5];
// reduce: accumulate into a single value
$sum = array_reduce($numbers, fn($acc, $n) => $acc + $n, 0);
// 15
$concat = array_reduce(["a","b","c"], fn($acc, $s) => $acc . $s, "");
// "abc"
// keys and values
$arr = ["name" => "Anna", "age" => 30];
array_keys($arr); // ["name", "age"]
array_values($arr); // ["Anna", 30]
// in_array (check existence)
in_array("Anna", $arr); // true
in_array("Anna", $arr, true); // true (strict)
array_key_exists("name", $arr); // truearray_reduce() accumulates all elements into a single value. array_keys()/array_values() extract keys/values. in_array() checks if a value exists. array_key_exists() checks a key.
array_column and array_multisort
$users = [
['name' => 'Anna', 'age' => 30],
['name' => 'Ray', 'age' => 25],
['name' => 'Eve', 'age' => 35],
];
// Extract column
$names = array_column($users, 'name');
// ['Anna', 'Ray', 'Eve']
// Reindex by column
$byName = array_column($users, null, 'name');
// ['Anna' => [...], 'Ray' => [...], ...]
// Sort by column
array_multisort(
array_column($users, 'age'), SORT_ASC,
$users
);array_column() extracts a column from a multidimensional array. With null the the 2nd arg, it reindexes by the column. array_multisort() sorts the original array by a specific column.
array_flip and array_search
// array_flip: swaps keys ↔ values
$map = ['a' => 1, 'b' => 2, 'c' => 3];
array_flip($map); // [1 => 'a', 2 => 'b', 3 => 'c']
// array_search: finds key by value
$fruits = ['apple', 'banana', 'cherry'];
array_search('banana', $fruits); // 1
array_search('x', $fruits); // false
// array_key_exists vs isset
$arr = ['name' => null];
array_key_exists('name', $arr); // true
isset($arr['name']); // false (it is null!)
// array_rand: random key(s)
array_rand($fruits); // 0, 1 or 2
array_rand($fruits, 2); // [0, 2] (2 keys)array_flip() swaps keys and values. array_search() returns the key of the found value (or false). array_key_exists() checks a key even with a null value (unlike isset()). array_rand() returns random key(s).
Accessing and Modifying
$arr = ["a" => 1, "b" => 2, "c" => 3]; // Access $arr["a"]; // 1 $arr["b"] ?? 0; // 2 (with default) // Modify $arr["d"] = 4; // add $arr["a"] = 10; // update unset($arr["b"]); // remove // Add to end/start $arr[] = 5; // end (push) array_push($arr, 6, 7); // end (multiple) array_unshift($arr, 0); // start // Remove from end/start $last = array_pop($arr); $first = array_shift($arr);
$arr[] adds to the end. unset() removes an element. array_push()/array_pop() for the end. array_unshift()/array_shift() for the start. ?? avoids an error if the key does not exist.
Sorting
$numbers = [3, 1, 4, 1, 5]; sort($numbers); // [1, 1, 3, 4, 5] (loses keys) rsort($numbers); // [5, 4, 3, 1, 1] (reverse) // Preserving keys asort($numbers); // sorts by value arsort($numbers); // reverse by value ksort($numbers); // sorts by key krsort($numbers); // reverse by key // Custom sorting $users = [["name" => "B"], ["name" => "A"]]; usort($users, fn($a, $b) => strcmp($a["name"], $b["name"])); // Sort by column array_multisort(array_column($users, "name"), SORT_ASC, $users);
sort() sorts values (loses keys). asort() preserves keys. usort() accepts a custom callback. array_multisort() sorts by a column of a multidimensional array. Prefix r = reverse, k = by key.
array_intersect and array_diff
$a = [1, 2, 3, 4, 5]; $b = [3, 4, 5, 6, 7]; // Intersection (common) array_intersect($a, $b); // [3, 4, 5] // Difference (in $a but not in $b) array_diff($a, $b); // [1, 2] // Associative $x = ['a' => 1, 'b' => 2]; $y = ['b' => 2, 'c' => 3]; array_intersect_assoc($x, $y); // ['b' => 2] array_diff_key($x, $y); // ['a' => 1] // Combine with spread $common = [...array_intersect($a, $b)];
array_intersect() returns common values. array_diff() returns values that are in the 1st but not in the others. Suffixes: _key compares keys, _assoc compares both. They preserve the keys of the 1st array.
Iterating Arrays
$fruits = ["apple", "banana", "pear"];
// Simple foreach
foreach ($fruits as $fruit) {
echo $fruit;
}
// foreach with key
foreach ($fruits as $i => $fruit) {
echo "$i: $fruit";
}
// Traditional for
for ($i = 0; $i < count($fruits); $i++) {
echo $fruits[$i];
}
// while with each (old)
// array_walk (callback)
array_walk($fruits, function($val, $key) {
echo "$key => $val\n";
});foreach is the preferred way to iterate. $i => $val gives key and value. count() returns the size. array_walk() applies a callback to each element. Avoid modifying the array during iteration.
Merge, Slice and Chunk
$a = [1, 2, 3]; $b = [3, 4, 5]; // Merge $c = array_merge($a, $b); // [1,2,3,3,4,5] $d = [...$a, ...$b]; // spread (PHP 7.4+) // Associative: last key wins $x = ["a" => 1]; $y = ["a" => 2, "b" => 3]; array_merge($x, $y); // ["a" => 2, "b" => 3] // Slice (extract part) array_slice([1,2,3,4,5], 1, 3); // [2,3,4] // Chunk (split into groups) array_chunk([1,2,3,4,5], 2); // [[1,2], [3,4], [5]] // Unique array_unique([1, 2, 2, 3, 3]); // [1, 2, 3]
array_merge() combines arrays. Spread [...$a, ...$b] is more modern. array_slice() extracts a part. array_chunk() splits into groups. array_unique() removes duplicates.
array_walk and array_walk_recursive
$fruits = ['apple', 'banana', 'cherry'];
// array_walk: modifies by reference
array_walk($fruits, function (&$item, $key) {
$item = ucfirst($item);
});
// ['Apple', 'Banana', 'Cherry']
// With extra parameter
$prefix = 'Fruit: ';
array_walk($fruits, function (&$item) use ($prefix) {
$item = $prefix . $item;
});
// Recursive (nested arrays)
$nested = ['a' => [1, 2], 'b' => [3, 4]];
array_walk_recursive($nested, function (&$val) {
$val *= 2;
});
// ['a' => [2, 4], 'b' => [6, 8]]array_walk() applies a callback to each element (by reference). Unlike array_map(): it modifies the original and passes the key the the 2nd argument. array_walk_recursive() enters nested arrays.
array_map and array_filter
$numbers = [1, 2, 3, 4, 5];
// map: transform each element
$double = array_map(fn($n) => $n * 2, $numbers);
// [2, 4, 6, 8, 10]
// filter: keep those that pass
$even = array_filter($numbers, fn($n) => $n % 2 === 0);
// [2 => 2, 4 => 4]
// filter without callback (removes falsy)
$clean = array_filter([0, 1, "", "a", null, 2]);
// [1 => 1, 3 => "a", 5 => 2]
// map with keys preserved
$upper = array_map('strtoupper', ["ana", "rui"]);
// ["ANA", "RUI"]array_map() transforms each element and returns a new array. array_filter() keeps only those that pass the test. Without a callback, array_filter() removes falsy values. Both preserve the original keys.
Destructuring and Spread
// Destructuring (list)
[$a, $b, $c] = [1, 2, 3];
echo $a; // 1
// With keys
["name" => $name, "age" => $age] = [
"name" => "Anna", "age" => 30
];
// Skip elements
[, , $third] = [1, 2, 3];
// Spread in calls
function sum($a, $b, $c) { return $a+$b+$c; }
$args = [1, 2, 3];
sum(...$args); // 6
// Spread to create arrays
$base = [1, 2];
$extra = [...$base, 3, 4]; // [1,2,3,4]Destructuring [$a, $b] = [1, 2] extracts values. With keys: ["name" => $n]. Spread ... expands arrays into arguments or into another array. A lone comma [, , $x] skips positions.
array_unique and Counting
$arr = [1, 2, 2, 3, 3, 3];
// Remove duplicates
array_unique($arr); // [1, 2, 3]
array_values(array_unique($arr)); // reindex
// Counting
count($arr); // 6
sizeof($arr); // 6 (alias)
// Recursive counting
$nested = ['a' => [1, 2], 'b' => [3]];
count($nested, COUNT_RECURSIVE); // 5
// Count values
array_count_values(['a','b','a','c','a']);
// ['a' => 3, 'b' => 1, 'c' => 1]
// Check existence
in_array('banana', $fruits); // true/false
in_array('5', [5, 10], true); // false (strict)array_unique() removes duplicates (preserves keys — use array_values() to reindex). count() counts elements. array_count_values() counts occurrences. in_array() with 3rd arg true does a strict comparison.
Flow Control
If / Else / ElseIf
$age = 20;
if ($age >= 18) {
echo "Adult";
} elseif ($age >= 13) {
echo "Teenager";
} else {
echo "Child";
}
// Alternative syntax (templates)
<?php if ($active): ?>
<p>Active</p>
<?php else: ?>
<p>Inactive</p>
<?php endif; ?>
// Null coalescing chain
$val = $a ?? $b ?? $c ?? "default";if/elseif/else for conditions. The alternative syntax if: endif; is ideal in HTML templates. Chained ?? checks multiple variables. Use === in comparisons to avoid type coercion.
Comparison Operators
// Loose vs Strict 0 == "0"; // true (compares value) 0 === "0"; // false (compares value + type) null == false; // true null === false; // false // Spaceship 1 <=> 2; // -1 2 <=> 2; // 0 3 <=> 2; // 1 // Useful in usort $nums = [3, 1, 2]; usort($nums, fn($a, $b) => $a <=> $b); // Null coalescing vs ternary $val = $x ?? "default"; // null only $val = $x ?: "default"; // any falsy
Always prefer === (strict) over == (loose). <=> (spaceship) returns -1, 0 or 1 — ideal for usort(). ?? only triggers on null; ?: on any falsy value.
Nested Loops and Labels
// Nested loop with break/continue by level
for ($i = 1; $i <= 3; $i++) {
for ($j = 1; $j <= 3; $j++) {
if ($i === 2 && $j === 2) break 2;
echo "$i,$j ";
}
}
// 1,1 1,2 1,3 2,1
// Nested foreach
foreach ($categories as $cat) {
foreach ($cat->products as $prod) {
if ($prod->stock === 0) continue 2;
echo $prod->name;
}
}
// while with multiple conditions
$x = 0; $y = 10;
while ($x < 5 && $y > 3) {
$x++; $y--;
}break 2 and continue 2 act on the outer loop. The number indicates how many levels. In nested loops, use continue 2 to skip the parent loop's iteration. Combine conditions with && or ||.
Switch and Match
// Classic switch
$day = "Monday";
switch ($day) {
case "Monday":
case "Tuesday":
echo "Start";
break;
case "Friday":
echo "End";
break;
default:
echo "Middle";
}
// Match (PHP 8+) — safer
$result = match($day) {
"Monday", "Tuesday" => "Start",
"Friday" => "End",
default => "Middle",
};switch compares with == (loose). match (PHP 8+) compares with === (strict) and returns a value. match does not need break and throws an error if no case matches (without default).
Alternative Syntax (Templates)
<?php if ($loggedIn): ?>
<p>Welcome, <?= $name ?></p>
<?php else: ?>
<a href="/login">Enter</a>
<?php endif; ?>
<?php foreach ($items as $item): ?>
<li><?= htmlspecialchars($item) ?></li>
<?php endforeach; ?>
<?php while ($row = $stmt->fetch()): ?>
<p><?= $row['name'] ?></p>
<?php endwhile; ?>The alternative syntax if: endif;, foreach: endforeach;, while: endwhile; is ideal in HTML templates. More readable than braces when mixed with HTML. Use <?= for escaped output.
Match Expression (PHP 8) 8.0
// Switch: verbose, loose comparison
switch ($status) {
case 'active':
$label = 'Active';
break;
case 'inactive':
$label = 'Inactive';
break;
default:
$label = 'Unknown';
}
// Match: expression, strict, no break
$label = match($status) {
'active' => 'Active',
'inactive', 'suspended' => 'Blocked',
default => throw new Exception("Status invalid"),
};match (PHP 8+) is an expression that returns a value. It compares with === (strict). No break needed. Multiple cases with commas. Without default, it throws UnhandledMatchError if nothing matches.
For and Foreach
// Traditional for
for ($i = 0; $i < 10; $i++) {
echo $i;
}
// for with multiple variables
for ($i = 0, $j = 10; $i < $j; $i++, $j--) {
echo "$i $j";
}
// foreach (arrays)
foreach ([1, 2, 3] as $val) {
echo $val;
}
// foreach by reference
$arr = [1, 2, 3];
foreach ($arr as &$val) {
$val *= 2; // modifies the original
}
unset($val); // important!for when you know the number of iterations. foreach for arrays/objects. &$val iterates by reference (modifies the original) — do unset($val) afterwards to avoid bugs.
Break, Continue and Goto
// break with level (nested loops)
for ($i = 0; $i < 10; $i++) {
for ($j = 0; $j < 10; $j++) {
if ($i * $j > 20) break 2; // exits both
echo "$i x $j = " . ($i*$j);
}
}
// continue with level
for ($i = 0; $i < 5; $i++) {
for ($j = 0; $j < 5; $j++) {
if ($j === 2) continue 2; // skips the outer for
}
}
// goto (avoid!)
goto end;
echo "This does not run";
end:
echo "Jumped to here";break 2 exits 2 nested loops. continue 2 skips to the next iteration of the outer loop. goto exists but avoid it — it makes code hard to follow. Prefer functions and clear conditions.
While and Do-While
// while
$n = 0;
while ($n < 5) {
echo $n++;
}
// do-while (runs at least once)
do {
$input = readline("Name: ");
} while (empty($input));
// Infinite loop with break
$i = 0;
while (true) {
if ($i >= 10) break;
echo $i++;
}
// continue (skip iteration)
for ($i = 0; $i < 10; $i++) {
if ($i % 2 === 0) continue;
echo $i; // only odd numbers
}while repeats while the condition is true. do-while runs at least once. break exits the loop. continue skips to the next iteration. Beware of infinite loops!
declare and Ticks
// strict_types: forces exact types
declare(strict_types=1);
function sum(int $a, int $b): int {
return $a + $b;
}
sum(1, "2"); // TypeError!
// ticks: runs every N statements
declare(ticks=1);
register_tick_function(function () {
echo "Tick executed\n";
});
// encoding (PHP 8.3+)
// declare(encoding='UTF-8');declare(strict_types=1) must be the 1st line of the file. It forces exact type hints (no coercion). ticks runs a callback every N statements (useful for profiling). strict_types is per file, not global.
Functions
Defining Functions
// Basic function
function greet(string $name): string {
return "Hello, $name!";
}
// Parameter with default
function power(int $base, int $exp = 2): int {
return $base ** $exp;
}
// No return (void)
function log(string $msg): void {
file_put_contents('app.log', $msg . PHP_EOL, FILE_APPEND);
}
// Nullable return
function find(int $id): ?string {
return $id === 1 ? "Anna" : null;
}Use type hints on parameters and return. ?string allows null. void indicates no return. Defaults with = value. Function names are case-insensitive.
Recursion and Memoization
// Recursive factorial
function factorial(int $n): int {
return $n <= 1 ? 1 : $n * factorial($n - 1);
}
// Fibonacci with memoization
function fib(int $n, array &$cache = []): int {
if ($n <= 1) return $n;
return $cache[$n] ??= fib($n-1, $cache) + fib($n-2, $cache);
}
// Recursion with array
function flatten(array $arr): array {
$result = [];
foreach ($arr as $item) {
is_array($item)
? $result = [...$result, ...flatten($item)]
: $result[] = $item;
}
return $result;
}Recursion: a function that calls itself. Always have a base case to stop. &$cache by reference for memoization. ??= computes and stores only if it does not exist.
Variable and Dynamic Functions
// Variable function
$fn = 'strtoupper';
echo $fn('hello'); // "HELLO"
// Dynamic name
$action = $_GET['action'] ?? 'list';
$functions = ['list' => 'listItems', 'create' => 'createItem'];
if (isset($functions[$action])) {
$functions[$action]();
}
// call_user_func
call_user_func('strtoupper', 'hello');
call_user_func_array('array_merge', [[1], [2]]);
// function_exists
if (function_exists('array_is_list')) {
// PHP 8.1+
}Variable functions: store the name in a variable and call with $fn(). call_user_func() calls by name. call_user_func_array() passes args the an array. function_exists() checks availability. Useful for dispatch patterns.
Arrow Functions and Closures
// Arrow function (PHP 7.4+)
$double = fn($x) => $x * 2;
$sum = fn($a, $b) => $a + $b;
// Automatic scope capture
$factor = 3;
$multiply = fn($x) => $x * $factor;
// Traditional closure
$counter = function() {
static $count = 0;
return ++$count;
};
// Closure with use
$prefix = "Dr.";
$greet = function($name) use ($prefix) {
return "$prefix $name";
};fn() => is an arrow function (captures variables automatically). function() use ($var) is a closure that imports variables from the scope. static preserves the value between calls.
Advanced Type Hints
// Union types (PHP 8+)
function format(int|float $num): string {
return number_format($num, 2);
}
// Nullable
function fetch(int $id): ?User {
return $id ? new User() : null;
}
// mixed (PHP 8+)
function dump(mixed $val): void {
var_dump($val);
}
// never (PHP 8.1+)
function abort(string $msg): never {
throw new RuntimeException($msg);
}
// strict mode
declare(strict_types=1);
function sum(int $a, int $b): int {
return $a + $b;
}
// sum(1, "2"); // TypeError!declare(strict_types=1) disables automatic coercion. Union types int|float accept multiple types. ?Type allows null. mixed accepts anything. never indicates it never returns.
Named Arguments (PHP 8) 8.0
// Before: order matters
array_slice($arr, 0, 5, true);
// Now: by name
array_slice(
array: $arr,
offset: 0,
length: 5,
preserve_keys: true
);
// Skip parameters with default
htmlspecialchars($input, double_encode: false);
// Combine positional + named
str_pad('5', length: 3, pad_string: '0');Named arguments (PHP 8+) allow passing parameters by name in any order. Ideal for functions with many optional parameters. You can skip defaults. After a named, all must be named.
Variadic Parameters and Spread
// Variadic (...args)
function sum(...$nums): int {
return array_sum($nums);
}
sum(1, 2, 3); // 6
// Spread to call
$args = [1, 2, 3];
sum(...$args); // 6
// Named arguments (PHP 8+)
function createUser(string $name, int $age = 0, bool $active = true) {}
createUser(name: "Anna", active: false);
// First-class callable (PHP 8.1+)
$strlen = strlen(...);
echo $strlen("hello"); // 3...$args accepts a variable number of arguments. ... (spread) expands arrays. Named arguments name: "Anna" (PHP 8+) allow skipping parameters. strlen(...) creates a first-class callable.
Anonymous Functions and Closures
// Closure with state
function counter(): Closure {
$count = 0;
return function() use (&$count) {
return ++$count;
};
}
$next = counter();
$next(); // 1
$next(); // 2
// IIFE (Immediately Invoked)
$result = (function() {
$x = calculate();
$y = process($x);
return $y;
})();
// Closure::fromCallable (PHP 7.1+)
$upper = Closure::fromCallable('strtoupper');
echo $upper('hello'); // "HELLO"Closures capture variables with use. &$var captures by reference (modifies the original). IIFE runs immediately. Closure::fromCallable() converts functions into closures. Useful for callbacks and factories.
Union Types and Mixed (PHP 8) 8.0
// Union types
function format(int|float $num): string {
return number_format($num, 2);
}
// Nullable shorthand
function find(int $id): ?User {
// returns User or null
}
// mixed (any type)
function debug(mixed $value): void {
var_dump($value);
}
// never (never returns)
function abort(string $msg): never {
throw new RuntimeException($msg);
}Union types int|float accept multiple types. ?Type is shorthand for Type|null. mixed accepts any type. never (PHP 8.1) indicates the function never returns (throws an exception or terminates).
Callbacks and Higher-Order
// Function the argument
function apply(callable $fn, array $arr): array {
return array_map($fn, $arr);
}
$result = apply(fn($x) => $x * 2, [1, 2, 3]);
// Return function
function multiplier(int $factor): Closure {
return fn($x) => $x * $factor;
}
$triple = multiplier(3);
echo $triple(5); // 15
// call_user_func
call_user_func('strtoupper', 'hello'); // "HELLO"
call_user_func_array('max', [1, 5, 3]); // 5callable is the type hint for functions/callbacks. array_map(), array_filter(), usort() accept callbacks. call_user_func() calls dynamically. Functions that return functions = higher-order.
First-Class Callable Syntax (PHP 8.1)
// Before: Closure::fromCallable or string
$fn = Closure::fromCallable('strlen');
$fn2 = 'strtoupper';
// PHP 8.1+: syntax (...)
$len = strlen(...);
echo $len('Hello'); // 3
$upper = strtoupper(...);
$nums = array_map(abs(...), [-1, -2, 3]);
// [1, 2, 3]
// Object methods
$obj = new MyClass();
$method = $obj->doSomething(...);
$method('arg');
// Static methods
$static = MyClass::create(...);The syntax function(...) (PHP 8.1+) creates a Closure from any callable. Replaces Closure::fromCallable(). Works with functions, object methods ($obj->method(...)) and static methods (Class::method(...)).
Fibers and Generators 8.0
// Generator (lazy evaluation)
function infinite_range(): Generator {
$i = 0;
while (true) {
yield $i++;
}
}
foreach (infinite_range() as $n) {
if ($n > 5) break;
echo $n; // 0, 1, 2, 3, 4, 5
}
// Fiber (PHP 8.1+)
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('pause');
echo "Received: $value";
});
$result = $fiber->start(); // 'pause'
$fiber->resume('data'); // "Received: data"Generator with yield is lazy — it only computes when requested. Ideal for infinite sequences and large files. Fiber (PHP 8.1+) allows pausing/resuming execution. Foundation of asynchronous systems.
OOP
Classes and Objects
class User {
public string $name;
private int $age;
protected float $balance = 0;
public function __construct(
string $name,
int $age
) {
$this->name = $name;
$this->age = $age;
}
public function greet(): string {
return "Hello, {$this->name}";
}
}
$user = new User("Anna", 30);
echo $user->greet();public accessible from outside. private only within the class. protected class + children. $this references the instance. __construct() is the constructor. new creates instances.
Constructor Promotion (PHP 8)
// Before (PHP 7)
class UserOld {
private string $name;
private int $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
}
// Now (PHP 8+)
class User {
public function __construct(
private string $name,
private int $age,
private bool $active = true,
) {}
public function getName(): string {
return $this->name;
}
}Constructor Promotion (PHP 8+) declares and assigns properties directly in the constructor. private string $name in the parameter creates the property automatically. Significantly reduces boilerplate.
Namespaces and Autoload
// src/Models/User.php
namespace App\Models;
class User {
public function __construct(
private string $name
) {}
}
// index.php
require 'vendor/autoload.php';
use App\Models\User;
use App\Services\{EmailService, LogService};
$user = new User("Anna");
// Alias
use App\Models\User as AppUser;
use function App\Helpers\format;
use const App\Config\MAX_SIZE;namespace organizes classes intthe packages. use imports classes. autoload (Composer) loads classes automatically. use function and use const import functions/constants. PSR-4: 1 class = 1 file.
Final, Visibility and Constants
class Base {
final public function fixo(): void {}
// Cannot be overridden
}
final class Immutable {
// Cannot be extended
}
class Config {
// Visibility on constants (PHP 7.1+)
public const VERSION = '2.0';
protected const DB_HOST = 'localhost';
private const SECRET = 'abc123';
// Constant with expression (PHP 8.3+)
// const TOTAL = self::PRICE * 1.23;
}
// Access
echo Config::VERSION; // "2.0"final prevents method override or class extension. Class constants can have visibility (public, protected, private) since PHP 7.1. Access with Class::CONST. PHP 8.3 allows final on constants.
Intersection Types (PHP 8.1) 8.0
interface HasName {
public function getName(): string;
}
interface HasEmail {
public function getEmail(): string;
}
class Contact implements HasName, HasEmail {
public function getName(): string { return 'Anna'; }
public function getEmail(): string { return 'ana@mail.com'; }
}
// Intersection: must implement BOTH
function notify(HasName&HasEmail $c): void {
echo "{$c->getName()}: {$c->getEmail()}";
}
notify(new Contact()); // OK
// DNF types (PHP 8.2): union + intersection combination
function process((HasName&HasEmail)|null $c): void {}Intersection types A&B require the value to implement ALL interfaces. Different from union A|B (one or the other). PHP 8.2 introduces DNF types: (A&B)|C combines intersection inside union with parentheses.
Inheritance and Polymorphism
class Animal {
public function __construct(
protected string $name
) {}
public function sound(): string {
return "...";
}
}
class Dog extends Animal {
public function sound(): string {
return "{$this->name}: Au au!";
}
}
class Cat extends Animal {
public function sound(): string {
return "{$this->name}: Miau!";
}
}
// Polymorphism
$animals = [new Dog("Rex"), new Cat("Felix")];
foreach ($animals as $a) {
echo $a->sound(); // calls the correct method
}extends creates inheritance. The child class inherits methods and can override them (override). parent::method() calls the parent class method. Polymorphism: same interface, different behaviors.
Enums (PHP 8.1+)
enum Status: string {
case Active = 'active';
case Inactive = 'inactive';
case Pending = 'pending';
public function label(): string {
return match($this) {
self::Active => '✅ Active',
self::Inactive => '❌ Inactive',
self::Pending => '⏳ Pending',
};
}
}
$status = Status::Active;
echo $status->value; // "active"
echo $status->label(); // "✅ Active"
// Comparison
$status === Status::Active; // trueenum (PHP 8.1+) defines fixed sets of values. : string or : int for backed enums. ->value gives the value. They can have methods. Ideal for states, types and fixed categories.
Dependency Injection
// Without DI (coupled)
class OrderService {
private $db = new Database(); // bad!
}
// With DI (decoupled)
class OrderService {
public function __construct(
private DatabaseInterface $db,
private LoggerInterface $logger,
) {}
public function create(Order $order): void {
$this->db->save($order);
$this->logger->info("Order created");
}
}
// Manual injection
$service = new OrderService(
new MySQLDatabase(),
new FileLogger()
);Dependency Injection: receive dependencies in the constructor instead of creating them inside. Program to interfaces, not implementations. Makes testing (mocks) and swapping implementations easier. Foundation of SOLID.
Readonly Properties (PHP 8.1+) 8.0
class Point {
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
}
$p = new Point(1.5, 2.5);
echo $p->x; // 1.5
$p->x = 3.0; // Error! Cannot modify readonly
// Readonly class (PHP 8.2+)
readonly class Config {
public function __construct(
public string $host,
public int $port,
) {}
}readonly (PHP 8.1+) allows writing only once. After assignment, it cannot be modified. Ideal for Value Objects and DTOs. readonly class (PHP 8.2+) applies it to all properties.
Interfaces and Abstract
interface Payable {
public function pay(float $value): bool;
public function getMethod(): string;
}
abstract class PaymentBase implements Payable {
abstract protected function validate(): bool;
public function pay(float $value): bool {
if (!$this->validate()) return false;
// process...
return true;
}
}
class CreditCard extends PaymentBase {
protected function validate(): bool {
return true;
}
public function getMethod(): string {
return "Card";
}
}interface defines a contract (signatures only). implements fulfills the contract. abstract class can have methods with a body. abstract method forces implementation in the child. A class can implement multiple interfaces.
Static and Constants
class Config {
const MAX_UPLOAD = 10_485_760; // 10MB
private static array $cache = [];
public static function get(string $key): mixed {
return self::$cache[$key] ?? null;
}
public static function set(string $key, mixed $val): void {
self::$cache[$key] = $val;
}
}
// Usage without instance
Config::set('debug', true);
Config::get('debug'); // true
Config::MAX_UPLOAD; // 10485760static belongs to the class (not the instance). Access with Class::method(). self:: references the class itself. const defines class constants. Useful for singletons, factories and utilities.
Late Static Binding
class Animal {
public static function create(): static {
return new static(); // called class
}
public function type(): string {
return static::class; // real class
}
}
class Dog extends Animal {}
$dog = Dog::create();
echo get_class($dog); // "Dog" (not "Animal")
echo $dog->type(); // "Dog"
// self vs static
// self:: → class where it is defined
// static:: → class that was calledstatic:: (Late Static Binding) refers to the called class, not the defining one. self:: always refers to the class where the method lives. new static() creates an instance of the child class. Essential for factories and inheritance.
Attributes (PHP 8) 8.0
// Define attribute
#[Attribute(Attribute::TARGET_CLASS)]
class Route {
public function __construct(
public string $path,
public string $method = 'GET',
) {}
}
// Use
#[Route('/users', method: 'POST')]
class UserController {
public function store() {}
}
// Read via Reflection
$ref = new ReflectionClass(UserController::class);
$attrs = $ref->getAttributes(Route::class);
$route = $attrs[0]->newInstance();
echo $route->path; // "/users"#[Attribute] (PHP 8+) replaces comment annotations. Native metadata readable via Reflection. Used in frameworks (Symfony, Laravel) for routes, validation, dependency injection.
Traits
trait Loggable {
public function log(string $msg): void {
echo "[" . static::class . "] $msg\n";
}
}
trait Timestampable {
public ?DateTime $createdAt = null;
public function markCreated(): void {
$this->createdAt = new DateTime();
}
}
class Post {
use Loggable, Timestampable;
public function __construct(
public string $title
) {
$this->markCreated();
$this->log("Post created");
}
}trait shares code between classes without inheritance. use TraitName inside the class. Multiple traits with use A, B. Solves PHP's "multiple inheritance". static::class gives the current class name.
Magic Methods
class Product {
private array $data = [];
public function __get(string $key): mixed {
return $this->data[$key] ?? null;
}
public function __set(string $key, mixed $val): void {
$this->data[$key] = $val;
}
public function __isset(string $key): bool {
return isset($this->data[$key]);
}
public function __toString(): string {
return json_encode($this->data);
}
public function __invoke(): void {
echo "Called the function";
}
}
$p = new Product();
$p->name = "Keyboard"; // __set
echo $p->name; // __get
echo $p; // __toStringMagic methods start with __. __get()/__set() for dynamic properties. __toString() when used the a string. __invoke() when called the a function. __construct(), __destruct() for lifecycle.
Object Cloning and __clone
class Cart {
public array $items = [];
public function __construct(
public string $client
) {}
public function __clone(): void {
// Deep copy of items
$this->items = array_map(
fn($i) => clone $i,
$this->items
);
}
}
$original = new Cart('Anna');
$original->items = [new Item('Book')];
$copy = clone $original;
$copy->client = 'Brian';
// $original->client stays "Anna"clone creates a shallow copy of the object. __clone() is called after the copy — use it for deep copying internal objects. Without clone, the assignment $b = $a copies the reference, not the object.
Advanced Enums (PHP 8.1) 8.0
// Backed enum (with values)
enum Status: string {
case Active = 'active';
case Inactive = 'inactive';
case Suspended = 'suspended';
public function label(): string {
return match($this) {
self::Active => 'Active ✅',
self::Inactive => 'Inactive ❌',
self::Suspended => 'Suspended ⏸',
};
}
public function canEdit(): bool {
return $this === self::Active;
}
}
$status = Status::from('active');
$status->value; // 'active'
$status->name; // 'Active'
$status->label(); // 'Active ✅'
Status::tryFrom('x'); // null (no error)Enums with : string or : int are backed (they have a value). from() converts from a value (throws on invalid). tryFrom() returns null. They can have methods and use match. They replace magic constants.
Files and JSON
Reading and Writing Files
// Read everything at once
$content = file_get_contents('data.txt');
// Write (overwrites)
file_put_contents('output.txt', 'Hello!');
// Append (add to the end)
file_put_contents('log.txt', "line\n", FILE_APPEND);
// Read the array of lines
$lines = file('data.txt', FILE_IGNORE_NEW_LINES);
// Check existence
if (file_exists('config.ini')) {
$ini = parse_ini_file('config.ini');
}file_get_contents() reads everything. file_put_contents() writes. FILE_APPEND adds without overwriting. file() returns an array of lines. file_exists() checks before reading.
SplFileObject and Streams
// Read line by line (efficient)
$file = new SplFileObject('big.csv');
$file->setFlags(SplFileObject::READ_CSV);
foreach ($file as $line) {
[$name, $email] = $line;
echo "$name: $email\n";
}
// fopen/fread/fwrite
$fp = fopen('data.txt', 'r');
while (!feof($fp)) {
$line = fgets($fp);
}
fclose($fp);
// php://input (raw body)
$body = file_get_contents('php://input');SplFileObject is object-oriented and efficient for large files. fopen()/fgets() for manual reading. php://input reads the raw request body. Always close with fclose().
File Locking and Atomicity
// Exclusive lock (write)
$fp = fopen('counter.txt', 'c+');
if (flock($fp, LOCK_EX)) {
$val = (int) stream_get_contents($fp);
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, (string)($val + 1));
fflush($fp);
flock($fp, LOCK_UN);
}
fclose($fp);
// Shared lock (read)
$fp = fopen('data.txt', 'r');
flock($fp, LOCK_SH);
$content = stream_get_contents($fp);
flock($fp, LOCK_UN);
fclose($fp);
// Atomic write with rename
file_put_contents('tmp_' . $f, $data);
rename('tmp_' . $f, $f); // atomicflock() prevents race conditions. LOCK_EX = exclusive (write), LOCK_SH = shared (read). Always do fflush() before LOCK_UN. rename() is atomic on the same filesystem — ideal for safe writing.
JSON
// Array → JSON
$data = ['name' => 'Anna', 'age' => 30];
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// JSON → Array
$arr = json_decode($json, true); // true = array
$obj = json_decode($json); // stdClass
// Handle errors
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception(json_last_error_msg());
}
// PHP 8+: named + flags
json_encode($data, flags: JSON_THROW_ON_ERROR);json_encode() converts to JSON. json_decode($json, true) to an associative array. JSON_PRETTY_PRINT formats. JSON_UNESCAPED_UNICODE preserves accents. JSON_THROW_ON_ERROR throws an exception on error.
CSV and Large Files
// Write CSV
$fp = fopen('data.csv', 'w');
fputcsv($fp, ['Name', 'Email']);
fputcsv($fp, ['Anna', 'ana@mail.com']);
fclose($fp);
// Read CSV
$fp = fopen('data.csv', 'r');
$headers = fgetcsv($fp);
while (($row = fgetcsv($fp)) !== false) {
$data[] = array_combine($headers, $row);
}
fclose($fp);
// Large file (line by line)
$handle = fopen('gigante.log', 'r');
while (($line = fgets($handle)) !== false) {
process($line); // constant memory
}
fclose($handle);fputcsv() writes CSV. fgetcsv() reads line by line. array_combine() maps headers to values. For large files, use fgets() in a loop — constant memory regardless of size.
File Manipulation
// Create folder
mkdir('uploads/2024', 0755, true); // recursive
// Copy, move, delete
copy('a.txt', 'b.txt');
rename('b.txt', 'c.txt'); // move/rename
unlink('c.txt'); // delete file
rmdir('folder-empty'); // delete folder
// Information
filesize('data.txt'); // bytes
filemtime('data.txt'); // last modification
pathinfo('photo.jpg', PATHINFO_EXTENSION); // "jpg"
// List folder
$files = glob('uploads/*.jpg');
$all = scandir('.');mkdir() with true creates recursively. rename() moves/renames. unlink() deletes files. glob() finds by pattern. pathinfo() extracts extension, name, folder.
Pathinfo and Path Manipulation
$path = '/var/www/uploads/photo.jpg';
pathinfo($path, PATHINFO_DIRNAME); // /var/www/uploads
pathinfo($path, PATHINFO_BASENAME); // photo.jpg
pathinfo($path, PATHINFO_FILENAME); // photo
pathinfo($path, PATHINFO_EXTENSION); // jpg
// Build paths
$destination = __DIR__ . '/uploads/' . $filename;
// Normalize
realpath('./../file.txt');
basename('/path/to/file.php'); // file.php
dirname('/path/to/file.php'); // /path/to
// Temp
$tmp = tempnam(sys_get_temp_dir(), 'prefix_');pathinfo() extracts parts of the path. realpath() resolves . and ... basename() gives the file name. dirname() gives the folder. tempnam() creates a unique temporary file.
File Upload
// HTML: <form enctype="multipart/form-data">
// <input type="file" name="photo">
if (isset($_FILES['photo'])) {
$f = $_FILES['photo'];
if ($f['error'] !== UPLOAD_ERR_OK) {
die("Error: " . $f['error']);
}
$ext = pathinfo($f['name'], PATHINFO_EXTENSION);
$permitidas = ['jpg', 'png', 'webp'];
if (!in_array(strtolower($ext), $permitidas)) {
die("Type not allowed");
}
$destination = 'uploads/' . uniqid() . '.' . $ext;
move_uploaded_file($f['tmp_name'], $destination);
}$_FILES contains the upload data. move_uploaded_file() moves it to the final destination. Always validate: error, extension, size. Use uniqid() for unique names. Never trust the original name.
DirectoryIterator and Glob
// glob: find files by pattern
$photos = glob('uploads/*.{jpg,png,webp}', GLOB_BRACE);
$phpFiles = glob(__DIR__ . '/*.php');
// DirectoryIterator (OOP)
$dir = new DirectoryIterator('/var/www');
foreach ($dir as $item) {
if ($item->isDot()) continue;
echo $item->getFilename();
echo $item->getSize();
echo $item->isDir() ? ' [DIR]' : ' [FILE]';
}
// RecursiveDirectoryIterator
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/src')
);
foreach ($it as $file) {
if ($file->getExtension() === 'php') {
echo $file->getPathname();
}
}glob() finds files by pattern (supports *, ?, {a,b}). DirectoryIterator is the OOP way to list directories. RecursiveDirectoryIterator traverses subdirectories recursively.
Database and Security
PDO — Connection
try {
$pdo = new PDO(
'mysql:host=localhost;dbname=app;charset=utf8mb4',
'root',
'password',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
die("Error na connection: " . $e->getMessage());
}PDO supports MySQL, PostgreSQL, SQLite, etc. ERRMODE_EXCEPTION throws exceptions on errors. FETCH_ASSOC returns associative arrays. charset=utf8mb4 for full Unicode.
Sessions and Cookies
// Start session
session_start();
// Store/read data
$_SESSION['user_id'] = 42;
$_SESSION['name'] = 'Anna';
$userId = $_SESSION['user_id'] ?? null;
// Destroy session
session_destroy();
$_SESSION = [];
// Cookies
setcookie('token', $value, [
'expires' => time() + 86400,
'path' => '/',
'httponly' => true,
'secure' => true,
'samesite' => 'Strict',
]);
$token = $_COOKIE['token'] ?? null;session_start() starts the session (before any output). $_SESSION stores data on the server. setcookie() creates cookies on the client. httponly prevents access via JS. secure only over HTTPS.
SQLite and Advanced PDO
// SQLite (no server)
$pdo = new PDO('sqlite:data.db');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Last inserted ID
$pdo->prepare("INSERT INTO users (name) VALUES (?)")
->execute(['Anna']);
$id = $pdo->lastInsertId();
// Multiple rows
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch modes
$stmt->fetch(PDO::FETCH_OBJ); // stdClass
$stmt->fetch(PDO::FETCH_COLUMN); // 1st column
$stmt->fetchAll(PDO::FETCH_KEY_PAIR); // id => name
// quote (avoid — use prepared)
$pdo->quote("O'Brien"); // "O''Brien"sqlite: creates a file-based DB (no server). lastInsertId() returns the generated ID. fetchAll() returns all rows. FETCH_KEY_PAIR creates a key→value map. quote() escapes but always prefer prepared statements.
Prepared Statements
// Safe INSERT
$stmt = $pdo->prepare(
"INSERT INTO users (name, email) VALUES (:name, :email)"
);
$stmt->execute([
':name' => $name,
':email' => $email,
]);
$id = $pdo->lastInsertId();
// SELECT with parameters
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();
// Multiple results
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll();prepare() + execute() prevents SQL Injection. Use placeholders :name or ?. fetch() returns 1 row. fetchAll() returns all. NEVER concatenate variables into the query!
PDO — UPDATE and DELETE
// UPDATE
$stmt = $pdo->prepare(
"UPDATE users SET name = :name, email = :email WHERE id = :id"
);
$stmt->execute([
':name' => $novoNome,
':email' => $novoEmail,
':id' => $userId,
]);
echo $stmt->rowCount(); // affected rows
// DELETE
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$userId]);
// Transaction
$pdo->beginTransaction();
try {
$pdo->exec("UPDATE contas SET balance = balance - 100 WHERE id = 1");
$pdo->exec("UPDATE contas SET balance = balance + 100 WHERE id = 2");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
}rowCount() returns affected rows. beginTransaction()/commit()/rollBack() for atomic transactions. If something fails, rollBack() undoes everything. Essential for operations that depend on each other.
Security — Hash and Escape
// Password hashing
$hash = password_hash($password, PASSWORD_DEFAULT);
// Verify password
if (password_verify($input, $hash)) {
echo "Password correta";
}
// Rehash if needed
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$novoHash = password_hash($password, PASSWORD_DEFAULT);
}
// Escape output (XSS)
echo htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
// CSRF token
$token = bin2hex(random_bytes(32));
$_SESSION['csrf'] = $token;password_hash() creates a secure hash (bcrypt). password_verify() compares. htmlspecialchars() prevents XSS. random_bytes() generates secure tokens. Never use md5() or sha1() for passwords!
Security Headers
// HTTP security headers
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Content-Security-Policy: default-src \'self\'');
header('Strict-Transport-Security: max-age=31536000');
header('Referrer-Policy: strict-origin-when-cross-origin');
// Cache
header('Cache-Control: no-store, no-cache');
header('Pragma: no-cache');
// Content type
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data);Security headers: X-Frame-Options prevents clickjacking. CSP controls allowed resources. HSTS forces HTTPS. X-Content-Type-Options prevents MIME sniffing. Always set the correct Content-Type.
Sanitization and Validation
// filter_var
$email = filter_var($input, FILTER_SANITIZE_EMAIL);
$url = filter_var($input, FILTER_SANITIZE_URL);
$int = filter_var($input, FILTER_VALIDATE_INT);
$emailOk = filter_var($input, FILTER_VALIDATE_EMAIL);
// filter_input (straight from the request)
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
// Manual validation
$name = trim($_POST['name'] ?? '');
if (mb_strlen($name) < 2 || mb_strlen($name) > 100) {
$errors[] = "Name invalid";
}
// htmlspecialchars on output
echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8');filter_var() sanitizes/validates data. FILTER_VALIDATE_* returns false if invalid. FILTER_SANITIZE_* cleans characters. Always validate on the server — never trust the client.
PDO Transactions
try {
$pdo->beginTransaction();
$pdo->prepare("UPDATE contas SET balance = balance - ? WHERE id = ?")
->execute([100, $deOrigem]);
$pdo->prepare("UPDATE contas SET balance = balance + ? WHERE id = ?")
->execute([100, $paraDestino]);
$pdo->prepare("INSERT INTO transfers (de, to, value) VALUES (?, ?, ?)")
->execute([$deOrigem, $paraDestino, 100]);
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}beginTransaction() starts a transaction. commit() confirms everything. rollBack() undoes everything if something fails. Essential for operations that depend on each other (transfers, orders). Always use try/catch with rollBack().
Numbers and Dates
Math Functions
abs(-5); // 5 max(1, 5, 3); // 5 min(1, 5, 3); // 1 round(3.7); // 4 floor(3.7); // 3 ceil(3.2); // 4 round(3.14159, 2); // 3.14 rand(1, 100); // random 1-100 random_int(1, 100); // cryptographically secure pow(2, 10); // 1024 sqrt(144); // 12 fmod(10, 3); // 1 (float remainder) intdiv(10, 3); // 3 (integer division)
round() rounds. floor() rounds down, ceil() rounds up. random_int() is cryptographically secure (prefer it over rand()). intdiv() does integer division. fmod() is the remainder for floats.
date() and time() Functions
// date() — quick formatting
echo date('d/m/Y'); // 25/12/2024
echo date('H:i:s'); // 14:30:00
echo date('Y-m-d H:i:s'); // 2024-12-25 14:30:00
echo date('l'); // Wednesday
echo date('F'); // December
// time() — current timestamp
$ts = time();
echo date('d/m/Y', $ts + 86400); // tomorrow
// strtotime() — string → timestamp
$ts = strtotime('+1 week');
$ts = strtotime('next monday');
$ts = strtotime('2024-12-25');
// Timezone
date_default_timezone_set('Europe/Lisbon');date() formats timestamps. time() gives the current timestamp. strtotime() converts strings into timestamps. date_default_timezone_set() sets the timezone. Formats: d (day), m (month), Y (year), H (hour).
Base Conversion and Hex
// Decimal → other bases
decbin(42); // "101010"
decoct(42); // "52"
dechex(255); // "ff"
// Other bases → decimal
bindec('101010'); // 42
octdec('52'); // 42
hexdec('ff'); // 255
// base_convert (any base 2-36)
base_convert('ff', 16, 2); // "11111111"
base_convert('255', 10, 16); // "ff"
// Literals in code
$bin = 0b1010; // 10
$oct = 0755; // 493
$hex = 0xFF; // 255
// Colors
$color = dechex(16711680); // "ff0000"decbin(), decoct(), dechex() convert decimal to binary/octal/hex. bindec(), octdec(), hexdec() do the reverse. base_convert() converts between bases 2-36. Literals: 0b binary, 0x hex, 0 octal.
Number Formatting
// number_format
number_format(1234567.891, 2, ',', '.');
// "1.234.567,89"
// sprintf
sprintf("%.2f", 19.9); // "19.90"
sprintf("%05d", 42); // "00042"
sprintf("%x", 255); // "ff" (hex)
sprintf("%b", 10); // "1010" (binary)
// Conversions
intval("42px"); // 42
floatval("3.14"); // 3.14
(int) "42"; // 42
(float) "3.14"; // 3.14
// Check
is_numeric("42"); // true
is_nan(acos(2)); // true
is_infinite(INF); // truenumber_format() formats with separators. sprintf() for advanced formatting. intval()/floatval() convert. is_numeric() checks whether it is a number or a numeric string.
Carbon-like Without Dependencies
// Useful date helpers
function today(): string {
return date('Y-m-d');
}
function tomorrow(): string {
return date('Y-m-d', strtotime('+1 day'));
}
function daysBetween(string $from, string $to): int {
$d1 = new DateTime($from);
$d2 = new DateTime($to);
return (int) $d1->diff($d2)->format('%r%a');
}
function formatPt(DateTime $dt): string {
$months = ['', 'January', 'February', 'March',
'April', 'May', 'June', 'July',
'August', 'September', 'October',
'November', 'December'];
return $dt->format('d') . ' de ' .
$months[(int)$dt->format('n')] .
' de ' . $dt->format('Y');
}Utility date functions without external dependencies. strtotime() for quick calculations. DateTime::diff() for precise differences. %r%a gives signed days. For real projects, consider the Carbon library.
DateTime
// Current date
$now = new DateTime();
echo $now->format('d/m/Y H:i:s');
// Specific date
$natal = new DateTime('2024-12-25');
$custom = DateTime::createFromFormat('d/m/Y', '25/12/2024');
// Modify
$now->modify('+1 week');
$now->add(new DateInterval('P1M')); // +1 month
$now->sub(new DateInterval('P2D')); // -2 days
// Compare
if ($natal > $now) {
echo "Natal still not chegou";
}
// Timestamp
$ts = $now->getTimestamp();
$dt = (new DateTime())->setTimestamp($ts);DateTime is the modern way to work with dates. format() formats the output. modify() accepts strings like +1 week. DateInterval for precise intervals. P1M = 1 month, P2D = 2 days.
Random Numbers and UUID
// Secure randoms
random_int(1, 100); // secure int
bin2hex(random_bytes(16)); // hex 32 chars
base64_encode(random_bytes(32));
// UUID v4 (no dependencies)
function uuid4(): string {
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s',
str_split(bin2hex($data), 4));
}
echo uuid4();
// "550e8400-e29b-41d4-a716-446655440000"random_int() and random_bytes() are cryptographically secure. rand() and mt_rand() are NOT secure for tokens. UUID v4 can be generated with random_bytes() and bit manipulation.
Difference Between Dates
$start = new DateTime('2024-01-01');
$end = new DateTime('2024-12-25');
$diff = $start->diff($end);
echo $diff->days; // 359 (total days)
echo $diff->m; // 11 (months)
echo $diff->format('%m months e %d days');
// Age
$nascimento = new DateTime('1994-05-15');
$age = $nascimento->diff(new DateTime())->y;
// Carbon-like (no dependencies)
function diasAte(DateTime $target): int {
return (int) (new DateTime())->diff($target)->format('%r%a');
}diff() returns a DateInterval. ->days gives the total days. ->y, ->m, ->d give years, months, days. %r in format shows a negative sign if the date has already passed.
BCMath — Arbitrary Precision
// Float has precision problems
0.1 + 0.2 === 0.3; // false!
// BCMath: strings with exact precision
bcadd('0.1', '0.2', 10); // '0.3'
bcsub('10', '3.5', 2); // '6.50'
bcmul('1.5', '2.3', 4); // '3.4500'
bcdiv('10', '3', 20); // '3.33333333333333333333'
bcmod('10', '3'); // '1'
bccomp('1.001', '1.0', 3); // 1 (greater)
bcpow('2', '10'); // '1024'
bcsqrt('144', 2); // '12.00'
// Global scale
bcscale(4);
echo bcdiv('1', '3'); // '0.3333'BCMath does arbitrary-precision arithmetic via strings. Essential for money and scientific calculations. bcscale() sets global decimals. Never use float for monetary values — use bcadd(), bcmul(), etc.
Errors and Debug
Try / Catch / Finally
try {
$result = 10 / 0;
} catch (DivisionByZeroError $e) {
echo "Error: " . $e->getMessage();
} catch (Exception $e) {
echo "Generic: " . $e->getMessage();
} finally {
echo "Always executa";
}
// Multiple types
try {
// ...
} catch (InvalidArgumentException | RuntimeException $e) {
echo $e->getMessage();
}
// PHP 8: non-capturing catch
try {
// ...
} catch (Exception) {
echo "Algo failed";
}try runs risky code. catch captures specific exceptions. finally always runs. PHP 8: catch (Exception) without a variable. Order from most specific to most generic.
Logging and Error Reporting
// error_log to file
error_log("Error: " . $e->getMessage(), 3, '/var/log/app.log');
// error_log with context
error_log(json_encode([
'level' => 'error',
'message' => $msg,
'context' => ['user_id' => $userId],
'time' => date('c'),
]));
// Per-environment configuration
// Development:
ini_set('display_errors', '1');
error_reporting(E_ALL);
// Production:
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php-errors.log');error_log() writes to the system log or a file. In development: display_errors = 1. In production: display_errors = 0 + log_errors = 1. Never show errors to the user in production.
Throwing Exceptions
// Throw exception
function divide(int $a, int $b): float {
if ($b === 0) {
throw new InvalidArgumentException(
"Divisor cannot be zero"
);
}
return $a / $b;
}
// Custom exception
class InsufficientBalanceException extends Exception {
public function __construct(
private float $balance,
private float $value
) {
parent::__construct(
"Balance {$this->balance} < {$this->value}"
);
}
}throw new Exception() throws exceptions. Create classes with extends Exception for specific errors. Include clear messages. The exception propagates until it finds a catch.
Exceptions with Context
// Chain exceptions (exception chaining)
try {
$data = $api->fetch($url);
} catch (HttpException $e) {
throw new ServiceException(
"Failure fetching data from the API",
previous: $e // PHP 8: named arg
);
}
// Exception with extra data
class ValidationException extends Exception {
public function __construct(
private array $errors,
string $message = "Validation failed"
) {
parent::__construct($message);
}
public function getErrors(): array {
return $this->errors;
}
}Exception chaining: pass the original exception the previous. getPrevious() accesses the cause. Create exceptions with extra data (e.g. $errors). Makes debugging and structured logging easier.
Error Handling
// Global handler
set_exception_handler(function (Throwable $e) {
error_log($e->getMessage());
echo "Error internal. Tente again.";
});
// Custom error handler
set_error_handler(function ($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
});
// Suppression (avoid!)
$result = @file_get_contents('nonexistent.txt');
// error_reporting
error_reporting(E_ALL);
ini_set('display_errors', '0'); // productionset_exception_handler() captures unhandled exceptions. set_error_handler() converts errors into exceptions. @ suppresses errors (avoid!). error_reporting(E_ALL) shows all. In production: log, don't display.
Custom Exception Classes
class AppException extends Exception {
public function __construct(
string $message,
private int $httpCode = 500,
?Throwable $previous = null
) {
parent::__construct($message, 0, $previous);
}
public function getHttpCode(): int {
return $this->httpCode;
}
}
class NotFoundException extends AppException {
public function __construct(string $resource, int $id) {
parent::__construct(
"$resource #$id not found",
httpCode: 404
);
}
}
throw new NotFoundException('User', 42);Create exception hierarchies with extends Exception. Add context (HTTP code, resource, etc.). Makes selective catch and proper HTTP responses easier. Pattern: one base exception per module + specific ones per case.
Debug and Var Dump
// var_dump (type + value)
var_dump([1, "a", null, true]);
// print_r (readable)
print_r($array);
// var_export (valid PHP code)
echo var_export($config, true);
// debug_backtrace
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
echo $trace[0]['file'] . ':' . $trace[0]['line'];
// error_log (writes to the log)
error_log("Debug: " . print_r($data, true));
// dd() (Laravel/Symfony)
// dd($variable); // dump + dievar_dump() shows type and value. print_r() is more readable for arrays. var_export() generates valid PHP code. debug_backtrace() shows the call stack. error_log() writes to the log without output.
set_error_handler and Shutdown
// Convert warnings into exceptions
set_error_handler(function (
int $severity,
string $message,
string $file,
int $line
): bool {
throw new ErrorException(
$message, 0, $severity, $file, $line
);
});
// Shutdown: captures fatal errors
register_shutdown_function(function () {
$error = error_get_last();
if ($error && in_array($error['type'],
[E_ERROR, E_PARSE, E_CORE_ERROR]
)) {
error_log(json_encode($error));
http_response_code(500);
}
});
// Restore handler
restore_error_handler();set_error_handler() converts warnings/notices into exceptions. register_shutdown_function() runs at the end — captures fatal errors via error_get_last(). restore_error_handler() restores the previous handler.
Tricks and Tips
Null Coalescing Operator (??)
// Basic $name = $_GET['name'] ?? 'Visitor'; // Chaining $val = $a ?? $b ?? $c ?? 'default'; // Null coalescing assignment (PHP 7.4+) $config['debug'] ??= false; // Only assigns if it does not exist or is null // In nested arrays $city = $user['address']['city'] ?? 'N/D'; // No error even if $user['address'] does not exist! // Combine with isset $items = $_SESSION['cart'] ?? [];
?? returns the first non-null value. It does not throw an error on missing keys. ??= assigns only if null. Replaces patterns like isset($x) ? $x : default. Chain it for multiple fallbacks.
Bitwise and Ternary Operators
// Bitwise
$a & $b; // AND
$a | $b; // OR
$a ^ $b; // XOR
~$a; // NOT
$a << 2; // shift left (×4)
$a >> 1; // shift right (÷2)
// Flags with bitwise
define('READ', 1); // 001
define('WRITE', 2); // 010
define('EXEC', 4); // 100
$perms = READ | WRITE; // 3 (011)
$perms & READ; // 1 (has READ)
$perms & EXEC; // 0 (no EXEC)
// Nested ternary (avoid!)
$x = $a ?: $b ?? $c; // confusingBitwise: & (AND), | (OR), ^ (XOR), ~ (NOT). << multiplies by 2, >> divides. Useful for flags/permissions. Combine with |, test with &.
Null-Safe Operator (?->)
// Without null-safe: chained checks
$city = null;
if ($user !== null) {
$addr = $user->getAddress();
if ($addr !== null) {
$city = $addr->getCity();
}
}
// With null-safe (PHP 8+)
$city = $user?->getAddress()?->getCity();
// If any link is null, returns null
$name = $user?->profile?->name ?? 'Anonymous';
// Works with methods and properties
$len = $user?->getName()?->length();The ?-> operator (PHP 8+) returns null if the object is null, without error. Replaces chains of if !== null. Combine with ?? for a default. Works with chained properties and methods.
Spread and Unpacking
// Merge arrays
$a = [1, 2];
$b = [3, 4];
$c = [...$a, ...$b]; // [1, 2, 3, 4]
// Associative (PHP 8.1+)
$defaults = ['color' => 'blue', 'size' => 'M'];
$custom = [...$defaults, 'color' => 'red'];
// ['color' => 'red', 'size' => 'M']
// In function calls
function sum($a, $b, $c) { return $a + $b + $c; }
$args = [1, 2, 3];
sum(...$args); // 6
// array_push with spread
$arr = [1, 2];
array_push($arr, ...[3, 4, 5]);Spread ... expands arrays. In associative arrays (PHP 8.1+), the last key wins. Replaces array_merge() with cleaner syntax. Works in function calls and array_push().
list() and Advanced Destructuring
// Classic list()
list($a, $b) = [1, 2];
// Short syntax
[$x, $y] = [10, 20];
// In foreach
$users = [['Anna', 30], ['Ray', 25]];
foreach ($users as [$name, $age]) {
echo "$name tem $age";
}
// Ignore elements
[, , $third] = [1, 2, 3, 4];
// Associative (PHP 7.1+)
['name' => $name, 'age' => $age] = $user;
// Swap
[$a, $b] = [$b, $a];list() or [] for destructuring. Works in foreach for multidimensional arrays. A lone comma ignores positions. Associative destructuring with ['key' => $var]. Elegant swap: [$a, $b] = [$b, $a].
Short Ternary and Advanced Elvis
// Elvis operator (?:)
$name = $_POST['name'] ?: 'Anonymous';
// Equivalent to: $_POST['name'] ? $_POST['name'] : 'Anonymous'
// vs Null Coalescing (??)
$name = $_POST['name'] ?? 'Anonymous';
// ?? only checks null; ?: checks falsy
// Practical difference
$value = 0;
$value ?: 10; // 10 (0 is falsy)
$value ?? 10; // 0 (0 is not null)
// Ternary in expressions
$class = $active ? 'active' : 'inactive';
$msg = $errors ? implode(', ', $errors) : 'OK';
// Nested ternary (avoid!)
// $x = $a ? 'a' : ($b ? 'b' : 'c');?: (Elvis) returns the value if it is truthy, otherwise the default. ?? only checks null. Difference: 0 ?: 10 gives 10, but 0 ?? 10 gives 0. Use ?? for optional values, ?: for truthy/falsy.
Array Tricks
// Last element $last = end($arr); $last = $arr[array_key_last($arr)]; // Remove duplicates $unico = array_unique([1, 2, 2, 3, 3]); // Flatten array $flat = array_merge(...[[1,2], [3,4]]); // or $flat = [...[1,2], ...[3,4]]; // array_combine (keys + values) $keys = ['a', 'b', 'c']; $vals = [1, 2, 3]; $arr = array_combine($keys, $vals); // array_flip (swap key ↔ value) $arr = ['a' => 1, 'b' => 2]; $flip = array_flip($arr); // [1 => 'a', 2 => 'b']
array_key_last() gives the last key. array_unique() removes duplicates. array_combine() creates an array from 2 arrays (keys + values). array_flip() swaps keys with values. Spread ... flattens arrays.
The @ Operator and Suppression
// Error suppression (avoid!)
$value = @$arr['key_nonexistent'];
$fp = @fopen('nonexistent.txt', 'r');
// Better alternatives:
$value = $arr['key'] ?? null;
if (file_exists('file.txt')) {
$fp = fopen('file.txt', 'r');
}
// Temporary error_reporting(0)
$old = error_reporting(0);
// ... code that may raise a warning ...
error_reporting($old);The @ operator suppresses errors — avoid it! It makes debugging hard and hides bugs. Prefer ?? for missing keys, file_exists() before opening, or try/catch. PHP 8: @ does not suppress fatal errors.
New PHP 8.x Functions 8.0
// str_contains, starts_with, ends_with
str_contains('Hello World', 'World');
str_starts_with('Hello', 'Ol');
str_ends_with('test.php', '.php');
// array_is_list (PHP 8.1)
array_is_list([1, 2, 3]); // true
array_is_list(['a' => 1]); // false
// enum_exists (PHP 8.1)
enum_exists(Status::class);
// fdiv (division without warning)
fdiv(1, 0); // INF (no error!)
// get_debug_type (PHP 8)
get_debug_type(null); // "null"
get_debug_type([]); // "array"
get_debug_type(1.5); // "float"PHP 8+: str_contains(), str_starts_with(), str_ends_with(). PHP 8.1: array_is_list(), enum_exists(), fdiv(). PHP 8: get_debug_type() shows the type in a readable way.
Strings — Tricks
// str_contains, starts_with, ends_with (PHP 8+)
str_contains('Hello World', 'World'); // true
str_starts_with('Hello', 'Ol'); // true
str_ends_with('file.php', '.php'); // true
// str_pad
str_pad('42', 5, '0', STR_PAD_LEFT); // "00042"
// strtr (multiple replacements)
strtr('Hello World', ['Hello' => 'Hi', 'World' => 'World']);
// sscanf (extract with format)
sscanf('2024-12-25', '%d-%d-%d', $y, $m, $d);
// wordwrap
echo wordwrap($text, 80, "\n");PHP 8+: str_contains(), str_starts_with(), str_ends_with() — more readable than strpos(). str_pad() pads. strtr() does multiple replacements at once. sscanf() extracts with a format.
Compact and Extract
// compact: variables → array
$name = 'Anna';
$age = 30;
$email = 'ana@mail.com';
$user = compact('name', 'age', 'email');
// ['name' => 'Anna', 'age' => 30, ...]
// extract: array → variables (careful!)
$data = ['title' => 'Post', 'author' => 'Anna'];
extract($data);
echo $title; // 'Post'
echo $author; // 'Anna'
// extract with prefix (safer)
extract($data, EXTR_PREFIX_ALL, 'd');
echo $d_title; // 'Post'compact() creates an array from variables. extract() does the reverse — careful: it overwrites existing variables! Use EXTR_PREFIX_ALL for safety. Prefer destructuring with [...] over extract().
PHP 8.2 and 8.3 New Features 8.0
// PHP 8.2: readonly classes
readonly class Point {
public function __construct(
public float $x,
public float $y,
) {}
}
// PHP 8.2: constants in traits
trait HasId {
public const TABLE = 'items';
}
// PHP 8.3: typed class constants
class Config {
final const int MAX = 100;
final const string APP = 'MyApp';
}
// PHP 8.3: json_validate()
json_validate('{"ok":true}'); // true
json_validate('{invalid}'); // false
// PHP 8.3: Randomizer
$rng = new Random\Randomizer();
$rng->getInt(1, 100);
$rng->shuffleArray([1, 2, 3]);PHP 8.2: readonly class, constants in traits, DNF types. PHP 8.3: final and types on constants, json_validate() without decode, Random\Randomizer for OOP randomness, #[\Override] attribute.