<?php
// PHP code here
?>
This tells the server where PHP code starts and ends.
echo "Hello World!";
print "Hello again!";
// echo is slightly faster, and can output multiple strings.
echo "One ", "Two";
Displays data (HTML, variables, etc.).
<?= "Hello" ?> // same as <?php echo "Hello"; ?>
This is the short echo tag (shorthand for echo). It's good for templating, but make sure short_open_tag is enabled (it's usually fine in modern environments).
// Single line
# Also single line
/* Multi-line
comment */
Comments.
$a = 5;
$b = 10;
Every statement ends with a semicolon.
// Variables are case-sensitive
$name != $Name
// Functions, keywords, and class names are not
ECHO "Hello"; // valid
Case sensitivity.
<html>
<body>
<h1>Welcome</h1>
<p><?php echo "Today is " . date("l"); ?></p>
</body>
</html>
PHP is often embedded in HTML files.
$name = "Alice";
$age = 25;
All variables in PHP start with a dollar sign.
$1name = "No"; // starts with number
$name-with-hyphen = "No"; // invalid character
Valid variable names:
$Name = "Alice";
$name = "Bob";
echo $Name; // Alice
echo $name; // Bob
Variables are case-sensitive.
$var = 5; // Integer
$var = "text"; // Now it's a string
PHP is loosely typed, meaning that it's not necessary to declare the type of a variable.
$foo = "bar";
$$foo = "Hello!";
echo $bar; // Outputs: Hello!
You can use the value of a variable as the name of another.
In PHP, primitive types are simple, indivisible values (not objects or arrays). They're passed by value (copied when assigned or passed into functions).
$name = "Alice";
String: a series of characters, where a character is the same as a byte.
$age = 30;
Integer: whole numbers, positive or negative.
$price = 19.99;
Float / Double: decimal (floating point) numbers.
$isLoggedIn = true;
$hasAccess = false;
Boolean: True or false values.
$user = null;
NULL: represents a variable with no value.
Non-primitive types are data structures or objects that can store multiple values or represent custom behavior. They're often passed by reference (especially objects).
// Indexed array
$colors = ["red", "green", "blue"];
echo $colors[0]; // red
// Associative array
$user = [
"name" => "Alice",
"age" => 30
];
echo $user["name"]; // Alice
Array: a collection of values stored under keys (numeric or associative). By default, arrays in PHP are passed by value, not by reference, but you can explicitly pass an array by reference using the reference operator (&), such as "function modifyArray(&$arr)".
class User {
public $name = "Alice";
public function greet() {
return "Hello, $this->name";
}
}
$user = new User();
echo $user->greet(); // Hello, Alice
Object: an instance of a class, representing custom structured data and behavior. Objects allow for OOP features like inheritance, encapsulation, and polymorphism. Objects are passed by reference, meaning changes inside a function affect the original object.
function sayHi() {
return "Hi!";
}
$func = "sayHi";
echo $func(); // Hi!
Callable: a reference to a function, method, or anonymous function. Can also be closures or methods passed around like variables.
function loopItems($items) {
foreach ($items as $item) {
echo $item;
}
}
Iterable: anything that can be looped over using foreach. This includes arrays, objects implementing Traversable (e.g. Iterator, Generator).
$file = fopen("data.txt", "r");
echo get_resource_type($file); // stream
Resource: A special variable holding a reference to external resources (like file handles, DB connections, etc.) Resources are not strings or objects; they're internally managed by PHP.
$age = 30;
is_int($age); // true
Returns whether the type of a variable is integer.
intval("42.7"); // 42
Returns the integer value of a variable.
$price = 19.99;
is_float($price); // true
Returns whether the type of a variable is float.
floatval("3.14"); // 3.14
Returns the float value of a variable.
$sum = "10" + 5; // 15 (string becomes integer)
PHP automatically converts between types during operations.
$price = 9.5;
echo "$" . number_format($price, 2); // $9.50
Formats a number with grouped thousands.
abs(-10); // 10
Returns absolute value.
round(4.3); // 4
round(4.6); // 5
Rounds to the nearest integer.
ceil(4.1); // 5
Rounds down.
// Method 1
pow(2, 3); // 8
// Method 2
2 ** 3; // 8
Returns x to the power of y.
sqrt(16); // 4
Returns the square root.
// Older method
rand(1, 10); // Random between 1 and 10
// Better method (Mersenne Twister, it's faster and more secure than rand())
mt_rand(1, 100); // Random between 1 and 100
Returns a random integer
max(3, 7, 2); // 7
max(array(3, 7, 2)); // 7
Returns the maximum value.
min(3, 7, 2); // 2
min(array(3, 7, 2)); // 2
Returns the minimum value.
$number = 42;
$binary = decbin($number); // 101010
Converts integer to its binary representation.
$binary = 101010;
$number = bindec($binary); // 42
Converts binary to integer.
$name = "Alice";
// Double quotes (parse variables)
echo "Hello $name"; // Hello Alice
// Single quotes (literal text)
echo 'Hello $name'; // Hello $name
Example of using strings.
$name = "Alice";
echo "Hello $name"; // Hello Alice
// For complex variables or arrays
echo "Hello {$user['name']}";
String interpolation works with double quotes.
$first = "Hello";
$second = "World";
echo $first . " " . $second; // Hello World
You can join strings using the . operator.
$name = "Alice";
$text = <<<EOD
Hello, $name
This is a heredoc string.\n
EOD;
echo $text;
Heredoc behaves like double-quoted strings, parses variables, escapes sequences like \n, and complex expressions.
The above example outputs:
Hello, Alice
This is a heredoc string.
$name = "Alice";
$text = <<<'EOD'
Hello, $name
This is a nowdoc string.\n
EOD;
echo $text;
Nowdoc behaves like single-quoted strings, does not parse variables, no escape sequence interpretation (everything is literal).
The above example outputs:
Hello, $name
This is a nowdoc string.\n
strlen("PHP"); // 3
Returns the number of bytes (characters in ASCII).
strtoupper("php"); // "PHP"
Converts to uppercase.
strtolower("HeLLo"); // "hello"
Converts to lowercase.
ucfirst("hello"); // "Hello"
Converts the first character to uppercase.
ucwords("hello world"); // "Hello World"
Converts first letter of each word to uppercase.
strrev("PHP"); // "PHP" → "PHP" (same)
strrev("abc"); // "cba"
Reverses a string.
trim(" hello "); // "hello"
Removes whitespace from both ends.
ltrim(" hello"); // "hello"
Removes whitespace from the left.
rtrim("hello "); // "hello"
Removes whitespace from the right.
substr("abcdef", 1, 3); // "bcd"
Returns a substring.
1 = start position
3 = length (optional)
strpos("hello", "e"); // 1
Finds position of first occurrence. Returns false if not found.
str_replace("world", "PHP", "Hello world"); // "Hello PHP"
Replaces all occurrences of a string in another string. It's case sensitive.
str_ireplace("WORLD", "PHP", "Hello world"); // "Hello PHP"
Case-insensitive replace.
explode(",", "a,b,c"); // ['a', 'b', 'c']
Splits a string into array.
implode("-", ['a', 'b', 'c']); // "a-b-c"
Joins array into string.
str_repeat("ha", 3); // "hahaha"
Repeats a string.
str_split("abc"); // ['a', 'b', 'c']
Splits a string into array of characters.
nl2br("Hello\nWorld"); // Hello<br />World
Converts newlines to <br>.
htmlspecialchars("<div>"); // "<div>"
Escapes HTML special characters.
addslashes("John's book"); // "John\'s book"
Returns a string with backslashes added before characters that need to be escaped, such as single quote ('), double quote ("), backslash (\), NUL (the NUL byte).
All arrays in PHP are actually implemented as ordered hash maps. Which means that keys (string or integer) are hashed, values are stored with references to these keys and the order is preserved. So, technically there's one array type in PHP, it just behaves differently based on how you structure your keys and values.
// Method 1
$colors = ["red", "green", "blue"];
// Method 2
$colors = array("red", "green", "blue");
An example of declaring an indexed array.
$user = [
"name" => "Alice",
"age" => 25
];
An example of declaring an associative array.
$users = [
["name" => "Alice", "age" => 25],
["name" => "Bob", "age" => 30]
];
An example of declaring a multidimensional array.
echo $colors[0]; // red
echo $user["name"]; // Alice
echo $users[1]["name"]; // Bob
Retrieves elements.
$colors[] = "yellow";
$user["email"] = "a@example.com";
Adds/updates elements.
unset($colors[1]);
Removes elements.
// Indexed
foreach ($colors as $color) {
echo $color;
}
// Associative
foreach ($user as $key => $value) {
echo "$key: $value";
}
// Multidimensional
foreach ($users as $user) {
foreach ($user as $key => $value) {
echo "$key: $value\n";
}
echo "---\n";
}
Loops through arrays.
$fruits = ["apple", "banana", "cherry"];
count($fruits); // 3
Counts elements in an array.
array_push($fruits, "orange", "grape");
// Equivalent to
$fruits[] = "orange";
Adds one or more elements to the end.
$last = array_pop($fruits);
$last; // "grape"
Removes and returns the last element.
$first = array_shift($fruits);
$first; // "apple"
Removes and returns the first element.
array_unshift($fruits, "kiwi", "lemon");
Adds elements to the beginning.
if (in_array("banana", $fruits)) {
echo "Found!";
}
Checks if a value exists.
$user = ["name" => "Alice"];
if (array_key_exists("name", $user)) {
echo "Key exists!";
}
Checks if a key exists.
array_keys($user); // ["name"]
Gets all keys.
array_values($user); // ["Alice"]
Gets all values.
sort($fruits);
Sorts values (indexed, ascending) by reindexing the array (keys reset to 0, 1, 2...).
rsort($fruits);
Sort an array in descending order.
asort($user);
Sorts by value (preserves keys).
ksort($user);
Sorts by key.
$colors1 = ["red", "green"];
$colors2 = ["blue", "yellow"];
$all = array_merge($colors1, $colors2);
Merges arrays.
$slice = array_slice($fruits, 1, 2); // elements at 1 and 2
Extracts part of an array.
$numbers = [1, 2, 3];
$squared = array_map(fn($n) => $n * $n, $numbers); // [1, 4, 9]
Applies a function to each element.
$numbers = [1, 2, 3];
$filtered = array_filter($numbers, fn($n) => $n > 1); // [2, 3]
Filters an array using a callback.
$numbers = [1, 2, 3];
$sum = array_reduce($numbers, fn($carry, $n) => $carry + $n, 0); // 6
Reduces to a single value.
$unique = array_unique([1, 2, 2, 3]); // [1, 2, 3]
Removes duplicate values.
$numbers = [1, 2, 3];
$reversed = array_reverse($numbers); // [3, 2, 1]
Reverses the order.
$first = [1, 2, 3];
$second = [4, 5, 6];
$combined = [...$first, ...$second];
print_r($combined); // [1, 2, 3, 4, 5, 6]
Merges arrays or combines values without array_merge() using the spread operator (...).
Superglobals are associative arrays. It's best to always validate and sanitize user input ($_GET, $_POST, etc.) to prevent XSS and injection attacks. You should avoid relying heavily on $_REQUEST or $GLOBALS. Use $_GET, $_POST, etc., explicitly so the code stays clear and secure. $_REQUEST can be confusing and insecure, it mixes sources, so use it carefully. $_SESSION requires session_start() at the beginning of the script.
Common superglobals:
// URL: script.php?name=John
$_GET['name']; // John
Example of $_GET.
// Form POST input
$_POST['email'];
Example of $_POST.
$_SERVER['HTTP_USER_AGENT'];
$_SERVER['REQUEST_METHOD'];
Example of $_SERVER.
// Upload field named 'myfile'
$_FILES['myfile']['name'];
Example of $_FILES.
Unlike variables constants don't start with $, they are global by default and their values cannot be changed or unset. It's conventional to use ALL_CAPS for constant names (e.g. API_KEY, MAX_LIMIT) and to utilise constants for app configuration, environment info and fixed values like roles or error codes.
define("SITE_NAME", "MyCoolSite");
echo SITE_NAME; // MyCoolSite
Creates a constant using "define()". The first argument is the constant name (by convention, uppercase). The second is the value. The constant name is case-sensitive by default.
define("COLORS", ["red", "green", "blue"]);
echo COLORS[0]; // red
Creates a constant with arrays using "define()". Prior to PHP 7, "define()" couldn't be used with arrays.
const MAX_USERS = 100;
echo MAX_USERS; // 100
Creates a constant using "const". Only valid at the top level of code (not inside if statements or loops). Can also be used inside classes.
class Config {
const VERSION = "1.0.0";
}
echo Config::VERSION;
This is an example of a class constant. Can be accessed without creating an instance. Cannot be changed.
// Defining a function
function greet() {
echo "Hello, world!";
}
// Calling a function
greet(); // Hello, world!
Basic syntax of a function.
function greetUser($name) {
echo "Hello, $name!";
}
greetUser("Alice"); // Hello, Alice!
Function with parameters.
function greetUser($name = "Guest") {
echo "Hello, $name!";
}
greetUser(); // Hello, Guest!
Function with default values.
function add($a, $b) {
return $a + $b;
}
$result = add(3, 5); // $result = 8
Returned values can be reused later.
$hello = function($name) {
return "Hello, $name!";
};
echo $hello("World"); // Hello, World!
// Can also be passed as arguments
function process($callback) {
echo $callback("PHP");
}
process(function($lang) {
return "I love $lang!";
}); // I love PHP!
Examples of anonymous functions (closures).
$square = fn($n) => $n * $n;
echo $square(4); // 16
Arrow functions (PHP 7.4+) have a shorter syntax and automatically inherit variables from the parent scope.
$greeting = "Hi";
function sayHi() {
echo $greeting // returns "Undefined variable $greeting" becasue $greeting is not accessible here unless made global
}
function sayGlobalHi() {
global $greeting;
echo $greeting; // Hi
}
Variables inside functions are local by default.
function multiply(int $a, int $b): int {
return $a * $b;
}
Type declarations (parameter type hints) help catch bugs early and make the code clearer.
function logMessage($level, ...$messages) {
foreach ($messages as $msg) {
echo "[$level] $msg\n";
}
}
logMessage("INFO", "Started", "Running", "Finished");
// [INFO] Started
// [INFO] Running
// [INFO] Finished
A variadic function can accept an arbitrary number of arguments. This is useful when you don't know in advance how many parameters will be passed. Behind the scenes the $messages variable becomes an array of all arguments passed.
function addOne(&$num) {
$num += 1;
}
$value = 10;
addOne($value);
echo $value; // Output: 11
You can pass variables by reference so that the function modifies the original variable. Without "&", PHP passes by value (a copy), and the original would remain unchanged.
function &getElement(&$array, $index) {
return $array[$index];
}
$arr = [1, 2, 3];
$ref = &getElement($arr, 1);
$ref = 99;
print_r($arr); // [1, 99, 3]
It's also possible to return by reference. This is rarely used but can be powerful in certain optimization cases.
function incrementAll(&...$nums) {
foreach ($nums as &$n) {
$n++;
}
}
$a = 1; $b = 2; $c = 3;
incrementAll($a, $b, $c);
echo "$a, $b, $c"; // Output: 2, 3, 4
Reference and variadic works well together.
$age = 20;
if ($age >= 18) {
echo "You are an adult.";
}
An example to an if statement.
$age = 16;
if ($age >= 18) {
echo "You are an adult.";
} else {
echo "You are a child.";
}
An example to an if-else statement.
$score = 85;
if ($score >= 90) {
echo "Grade: A";
} elseif ($score >= 80) {
echo "Grade: B";
} else {
echo "Grade: C or below";
}
An example to an if-elseif-else chain.
$isLoggedIn = true;
echo $isLoggedIn ? "Welcome back!" : "Please log in."; // Welcome back!
The ternary operator provides a compact syntax for simple if-else.
$name = $_GET['name'] ?? 'Guest';
echo "Hello, $name!";
The null coalescing operator (??) (PHP 7+) returns the left operand if it exists and is not null, otherwise returns the right operand.
$day = "Monday";
switch ($day) {
case "Monday":
echo "Start of the week!";
break;
case "Friday":
echo "Almost weekend!";
break;
default:
echo "It's just another day.";
}
A switch statement is used when there are multiple specific values to compare. "break" is required to prevent fall-through. "default" is optional but useful.
for ($i = 0; $i < 5; $i++) {
echo "Number: $i\n";
}
A for loop is best when you know exactly how many times to loop.
Initialization: $i = 0
Condition: $i < 5
Increment: $i++
$i = 0;
while ($i < 5) {
echo "Number: $i\n";
$i++;
}
A while loop loops as long as the condition is true. Useful when the number of iterations isn't known in advance.
$i = 0;
do {
echo "Number: $i\n";
$i++;
} while ($i < 5);
A do...while loop is like a "while" loop, but executes at least once, even if the condition is false.
// Without keys
$colors = ["red", "green", "blue"];
foreach ($colors as $color) {
echo "Color: $color\n";
}
// With keys
$person = ["name" => "Alice", "age" => 30];
foreach ($person as $key => $value) {
echo "$key: $value\n";
}
A foreach loop is best for iterating over arrays and objects.
for ($i = 1; $i <= 10; $i++) {
if ($i === 5) {
break; // Exit the loop when $i is 5
}
echo "Number: $i\n";
}
The "break" statement terminates the loop entirely when a condition is met.
for ($i = 1; $i <= 5; $i++) {
if ($i === 3) {
continue; // Skip when $i is 3
}
echo "Number: $i\n";
}
The "continue" statement skips the rest of the current iteration and moves to the next one.
class Car {
public $color;
public $brand;
public function drive() {
echo "The car is driving.\n";
}
}
A class is a blueprint for creating objects. It defines properties (variables) and methods (functions) that describe the behavior and characteristics of the object.
$myCar = new Car(); // Create object
$myCar->color = "Red";
$myCar->brand = "Toyota";
echo $myCar->brand; // Outputs: Toyota
$myCar->drive(); // Outputs: The car is driving.
An object is an instance of a class, a real, usable version of the blueprint.
class User {
public $username;
public function __construct($username) {
$this->username = $username;
}
}
$user = new User("john_doe");
echo $user->username; // Output: john_doe
A constructor (__construct()) is a special method that runs automatically when an object is created.
public function greet() {
echo "Hello, I am " . $this->name;
}
The "this" keyword refers to the current object instance.
class Animal {
public $name;
public function __construct($name) {
$this->name = $name;
}
public function speak() {
echo "$this->name makes a sound.\n";
}
}
class Dog extends Animal {
public function speak() {
echo "$this->name says: Woof!\n";
}
}
$dog = new Dog("Buddy");
$dog->speak(); // Buddy says: Woof!
Inheritance (using the "extends" keyword) allows a class to inherit properties and methods from another class.
class Dog extends Animal {
public function speak() {
parent::speak(); // Calls Animal's speak()
echo "$this->name barks!\n";
}
}
Using "parent::" you can call the parent's methods explicitly.
class Animal {
public $name = "Generic Animal"; // Accessible anywhere
protected $type = "Mammal"; // Accessible in class & subclasses
private $secret = "Hidden info"; // Accessible only within this class
public function showPublic() {
echo "Name: {$this->name}\n";
}
protected function showProtected() {
echo "Type: {$this->type}\n";
}
private function showPrivate() {
echo "Secret: {$this->secret}\n";
}
public function revealAll() {
// Can access all inside the class
$this->showPublic();
$this->showProtected();
$this->showPrivate();
}
}
class Dog extends Animal {
public function bark() {
echo "{$this->name} barks!\n"; // public - accessible
echo "{$this->type} dog.\n"; // protected - accessible
echo "{$this->secret}"; // Error: private - NOT accessible
}
public function revealFromChild() {
$this->showPublic(); // public method - accessible
$this->showProtected(); // protected method - accessible
$this->showPrivate(); // Error: private method - NOT accessible
}
}
$dog = new Dog();
$dog->showPublic(); // public method
$dog->revealAll(); // public method accessing all internally
$dog->bark(); // public - accessible
$dog->revealFromChild(); // public - accessible
echo $dog->type; // Error: protected property
echo $dog->secret; // Error: private property
$dog->showPrivate(); // Error: private method
Access modifiers are used to control visibility of class members:
public: Accessible from anywhere
protected: Accessible within the class and subclasses
private: Accessible only within the class
The static keyword is used to declare:
Static properties - shared across all instances of a class.
Static methods - callable without creating an object.
Late static binding - advanced usage with inheritance.
class Counter {
public static $count = 0;
public function increment() {
self::$count++;
}
}
$obj1 = new Counter();
$obj2 = new Counter();
$obj1->increment();
$obj2->increment();
echo Counter::$count; // Output: 2
A static property belongs to the class itself, not to any object. In the example above both $obj1 and $obj2 share the same static $count.
class Math {
public static function square($n) {
return $n * $n;
}
}
echo Math::square(5); // Output: 25
A static method can be called without an object, and it can only access static properties/methods, meaning that you can't use "$this" in a static method.
// Without late static binding
class ParentClass {
public static function who() {
echo "ParentClass\n";
}
public static function test() {
self::who(); // Bound to ParentClass
}
}
class ChildClass extends ParentClass {
public static function who() {
echo "ChildClass\n";
}
}
ChildClass::test(); // Output: ParentClass (Expected: ChildClass)
// With late static binding
class ParentClass {
public static function who() {
echo "ParentClass\n";
}
public static function test() {
static::who(); // Late static binding
}
}
class ChildClass extends ParentClass {
public static function who() {
echo "ChildClass\n";
}
}
ChildClass::test(); // Output: ChildClass
Late static binding allows you to reference the called class in a static inheritance context, even if the method you're in was defined in a parent class. This is done using the "static::" keyword instead of "self::". By default, "self::"" in a class refers to that exact class, not the class that may be calling the method via inheritance. This can be limiting. Using "static::"" allows PHP to defer resolution to the class that made the call, not where the method is defined.
The final keyword is used to restrict inheritance and method overriding, helping enforce OOP design rules where needed.
You can apply final to:
Classes: to prevent them from being extended.
Methods: to prevent them from being overridden in child classes.
final class Logger {
public function log($msg) {
echo "Log: $msg\n";
}
}
class FileLogger extends Logger {} // Fatal error
A final class cannot be extended. Use this when you want to seal a class and prevent subclassing (e.g. for security, stability, or performance).
class Base {
final public function connect() {
echo "Connecting...\n";
}
}
class Database extends Base {
// public function connect() {} // Fatal error
}
A final method cannot be overridden in child classes. It's used when you want to ensure that core behavior remains unchanged in all subclasses.
The "abstract" keyword is fundamental to object-oriented design when you want to define a blueprint for other classes without implementing everything yourself.
The abstract keyword is used for:
Abstract classes: cannot be instantiated.
Abstract methods: declared without a body, must be implemented by subclasses.
abstract class Animal {
public $name;
public function __construct($name) {
$this->name = $name;
}
// Abstract method, must be implemented by subclasses
abstract public function makeSound();
// Regular method
public function sleep() {
echo "$this->name is sleeping.\n";
}
}
An abstract class:
Cannot be instantiated directly.
Can contain both defined methods and abstract methods.
Is meant to be extended by child classes.
class Dog extends Animal {
public function makeSound() {
echo "$this->name says: Woof!\n";
}
}
$dog = new Dog("Buddy");
$dog->makeSound(); // Buddy says: Woof!
$dog->sleep(); // Buddy is sleeping.
An abstract method:
Is declared in an abstract class.
Has no body.
Must be implemented by the subclass using the same signature.
An interface defines a contract: it declares methods that any class implementing the interface must define. No method bodies (implementation). All methods must be public. Classes can implement multiple interfaces.
interface Logger {
public function log($message);
}
This says: "Any class that implements Logger must have a log() method that takes a parameter."
class FileLogger implements Logger {
public function log($message) {
echo "Logging to file: $message\n";
}
}
$logger = new FileLogger();
$logger->log("System started."); // Output: Logging to file: System started.
If the class fails to implement any method from the interface, PHP will throw a fatal error.
interface Logger {
public function log($message);
}
interface Notifier {
public function notify($message);
}
class SystemService implements Logger, Notifier {
public function log($message) {
echo "Log: $message\n";
}
public function notify($message) {
echo "Notify: $message\n";
}
}
PHP doesn't support multiple inheritance (i.e., a class can't extend two classes), but you can implement multiple interfaces.
function process(Logger $logger) {
$logger->log("Processing...");
}
Interfaces are great for dependency injection and type hinting. In this example you can pass in any object that implements Logger, allowing for loose coupling and easy testing.
A trait is a collection of methods and properties that can be injected into one or more classes. Traits cannot be instantiated. Traits cannot extend classes or implement interfaces. Classes can use multiple traits, even if they already extend a class. Traits are a powerful feature that allow you to reuse code across multiple classes without using inheritance. Think of traits as "horizontal" code sharing, compared to the "vertical" sharing you get with class inheritance.
Traits are best used when:
You need shared behavior across unrelated classes.
You want to avoid duplicating code.
You want to keep modularity and flexibility.
Traits should not be abused to replace good design. Use them for shared behavior, not as a dumping ground for random methods.
trait LoggerTrait {
public function log($msg) {
echo "[LOG]: $msg\n";
}
}
class App {
use LoggerTrait;
}
$app = new App();
$app->log("Application started."); // Output: [LOG]: Application started.
Basic trait example.
trait LoggerTrait {
public function log($msg) {
echo "[LOG]: $msg\n";
}
}
trait NotifierTrait {
public function notify($msg) {
echo "[NOTIFY]: $msg\n";
}
}
class App {
use LoggerTrait, NotifierTrait;
}
$app = new App();
$app->log("Hello");
$app->notify("You have a message.");
Example of using multiple traits.
trait A {
public function talk() {
echo "A says hi\n";
}
}
trait B {
public function talk() {
echo "B says hi\n";
}
}
class Speaker {
use A, B {
B::talk insteadof A; // Use B's version
A::talk as talkFromA; // Alias A's version
}
}
$s = new Speaker();
$s->talk(); // B says hi
$s->talkFromA(); // A says hi
If two traits have methods with the same name, you must resolve the conflict using "insteadof" and "as".
A namespace is a container for classes, interfaces, functions, and constants. It helps prevent naming collisions by grouping related code under a unique name. Think of it like a folder in a file system. A label that says, "This belongs to a specific group."
namespace MyApp\Utils;
class Logger {
public function log($msg) {
echo "[LOG]: $msg\n";
}
}
Basic syntax of a namespace. Save this in a file like "MyApp/Utils/Logger.php". The namespace declaration must be the first line in the file (before any other code, except declare()) and only one is allowed per file (unless using bracketed syntax).
// Fully qualified name
$logger = new \MyApp\Utils\Logger();
// Using "use" keyword (import)
use MyApp\Utils\Logger;
$logger = new Logger();
// Aliasing the class name
use MyApp\Utils\Logger as Log;
$log = new Log();
Example of using a class from a namespace.
namespace MyApp;
function test() {
echo "Inside MyApp namespace\n";
}
namespace {
// Global namespace
echo "This is in the global namespace.\n";
}
Example of mixing global and namespaced code.
// Without namespace (conflict)
class Logger {}
class Mailer {}
// You include a library with another Logger class = conflict
// With Namespace:
namespace MyApp;
class Logger {}
class Mailer {}
// Now you can use both
use MyApp\Logger;
use ThirdParty\Logger as ExternalLogger;
An example of the usefulness of namespaces.
$globalVar = "Hello";
function test() {
global $globalVar;
echo $globalVar; // Output: Hello
}
test();
With a global scope variables are declared outside any function or class. It's not accessible inside functions unless explicitly declared as global. In the example above, without global, $globalVar inside the function is undefined.
function sayHello() {
$msg = "Hello from inside!";
echo $msg;
}
sayHello();
// echo $msg; // Undefined variable
With a local scope variables declared inside a function are local to that function. They are not accessible outside of it.
function counter() {
static $count = 0;
$count++;
echo $count . "\n";
}
counter(); // 1
counter(); // 2
counter(); // 3
Static variables (function-level persistence) are declared with static inside a function. They retain their value between function calls.
function greet($name) {
echo "Hello, $name\n";
}
greet("Alice"); // Output: Hello, Alice
Function parameters (local scope) are parameters passed to functions that act like local variables.
echo $_SERVER['PHP_SELF'];
Superglobals (global scope by default) are special built-in variables like $_GET, $_POST, $_SESSION, etc. They are available everywhere in your code.
class Example {
public $a = "public";
protected $b = "protected";
private $c = "private";
public function show() {
echo $this->a;
echo $this->b;
echo $this->c;
}
}
$ex = new Example();
echo $ex->a; // Output: public
echo $ex->b; // Error
echo $ex->c; // Error
Class member scope (object-oriented PHP).
PHP uses access modifiers to define scope in classes:
public: accessible from anywhere
protected: accessible from class + subclasses
private: accessible from current class only
$x = 5;
function showX() {
echo $GLOBALS['x']; // 5
}
Global keyword vs $GLOBALS array: both let you access global variables inside functions, but $GLOBALS is a superglobal associative array.
| Superglobal | Description |
|---|---|
$_GET
|
Query parameters (URL) |
$_POST
|
Form data (submitted via POST) |
$_REQUEST
|
Combines $_GET, $_POST, and
$_COOKIE
|
$_SERVER
|
Info about headers, paths, and script |
$_FILES
|
File uploads |
$_COOKIE
|
Cookie data |
$_SESSION
|
Session data |
php://input
|
Raw body (used for JSON, PUT, etc.) |
// HTML from client
<form action="handler.php" method="POST">
<input type="text" name="username">
<input type="submit" value="Send">
</form>
// handler.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? 'Guest';
echo "Hello, $username!";
}
This is a basic example of handling POST requests.
// HTML from client
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="file" name="myFile">
<input type="submit">
</form>
// upload.php
if ($_FILES['myFile']['error'] === UPLOAD_ERR_OK) {
$tmp = $_FILES['myFile']['tmp_name'];
$name = basename($_FILES['myFile']['name']);
move_uploaded_file($tmp, "uploads/$name");
echo "Uploaded!";
}
This handles a file upload.
// API call from client
curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice"}' http://localhost/api.php
$data = json_decode(file_get_contents("php://input"), true);
echo "Hello, " . $data['name'];
Handles JSON payload.
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
// Handle GET
break;
case 'POST':
// Handle POST
break;
}
Detects request type.
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
It's never a good idea to trust user input. To sanitize, use:
filter_input()
htmlspecialchars()
filter_var()
| Mode | Description |
|---|---|
r
|
Read-only |
w
|
Write-only, truncates to zero |
a
|
Append |
x
|
Write, but error if file exists |
r+
|
Read and write |
a+
|
Read and append |
$handle = fopen("example.txt", "r");
while (!feof($handle)) {
$line = fgets($handle); // Read line by line
echo $line;
}
fclose($handle);
Reads from a file.
Other read functions:
fgets($handle) - Reads a line
fread($handle, $length) - Reads a specified number of bytes
file_get_contents("file.txt") - Reads entire file into a string (easy and common)
$handle = fopen("example.txt", "w");
fwrite($handle, "Hello, file!");
fclose($handle);
// Or a simpler way
file_put_contents("example.txt", "Hello again!");
// Use FILE_APPEND to add without overwriting
file_put_contents("example.txt", "New line\n", FILE_APPEND);
Writes to a file.
if (file_exists("example.txt")) {
echo "File exists!";
}
if (is_readable("example.txt")) { /* ... */ }
if (is_writable("example.txt")) { /* ... */ }
Checks file/directory status.
unlink("example.txt");
Deletes a file.
rename("old.txt", "new.txt");
Renames a file.
$lines = file("example.txt");
foreach ($lines as $line) {
echo $line;
}
Reads a file into an array.
// HTML
<form method="POST" enctype="multipart/form-data">
<input type="file" name="myfile">
<input type="submit">
</form>
// PHP
if ($_FILES['myfile']['error'] === UPLOAD_ERR_OK) {
$tmp = $_FILES['myfile']['tmp_name'];
$name = basename($_FILES['myfile']['name']);
move_uploaded_file($tmp, "uploads/$name");
echo "File uploaded!";
}
Basic example of handling file upload.
$handle = @fopen("nonexistent.txt", "r") or die("Cannot open file");
Error handling. It's better to use try/catch with error handling libraries (or with SPL classes like SplFileObject).
mkdir("new_folder");
Creates a directory.
rmdir("new_folder");
Removes an empty directory.
function deleteDirectory($dir) {
if (!file_exists($dir)) {
return false;
}
if (!is_dir($dir)) {
return unlink($dir);
}
foreach (scandir($dir) as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
deleteDirectory($path);
} else {
unlink($path);
}
}
return rmdir($dir);
}
// How to use
$deleted = deleteDirectory('path/to/folder');
if ($deleted) {
echo "Directory deleted successfully.";
} else {
echo "Failed to delete directory.";
}
if (is_file('path/to/item.txt')) {
echo "This is a file.";
}
if (is_dir('path/to/folder')) {
echo "This is a directory.";
}
Checks if a path is a directory.
scandir(".");
Lists files in a directory.
chdir("folder");
Changes working directory.
| Type | Description |
|---|---|
| Parse errors | Syntax issues that prevent the script from compiling |
| Fatal errors | Halt the script (e.g., calling an undefined function) |
| Warnings | Non-fatal, script continues (e.g., include missing file) |
| Notices | Minor issues (e.g., using an undefined variable) |
| Exceptions |
Object-oriented way to handle runtime errors via
try-catch
|
| Deprecation notices | Features that will be removed in future PHP versions |
error_reporting(E_ALL);
ini_set('display_errors', 1);
Turns on all errors.
@include("maybe_missing.php");
Suppresses a single error with @. It's best to avoid using @ too much as it hides real problems.
ini_set('log_errors', 1);
ini_set('error_log', 'errors.log');
error_log("Something went wrong!");
Instead of displaying errors, sometimes it's best to log them.
try {
throw new Exception("Something bad happened");
} catch (Exception $e) {
echo "Caught: " . $e->getMessage();
}
Modern PHP supports exceptions with try-catch blocks. Exceptions are especially useful in OOP and with libraries/frameworks.
set_error_handler('myErrorHandler');
set_exception_handler('myExceptionHandler');
function myErrorHandler($errno, $errstr, $errfile, $errline) {
echo "Custom Error: [$errno] $errstr in $errfile:$errline";
}
function myExceptionHandler($exception) {
echo "Custom Exception: " . $exception->getMessage();
}
You can define custom error and exception handlers.
try {
// Code that may throw
} catch (Throwable $t) {
echo "Caught: " . $t->getMessage();
}
Since PHP 7, both Error and Exception inherit from Throwable. You can catch fatal errors as shown in the example above.
Best practices:
A session is a way to store data across multiple page requests for a single user. Unlike cookies (which store data in the browser), session data is stored on the server, and each user gets a unique session ID (usually stored in a cookie).
Example use cases:
User authentication (keeping users logged in)
Shopping cart data
Form progress tracking
<?php
session_start();
// Store data in session
$_SESSION["username"] = "JohnDoe";
$_SESSION["role"] = "admin";
echo "Session data saved.";
?>
// Then, on another page:
<?php
session_start();
echo "Welcome, " . $_SESSION["username"]; // Output: Welcome, JohnDoe
?>
Basic example of using sessions.
// To log out or clear all session data:
session_start();
session_unset(); // Remove all session variables
session_destroy(); // Destroy the session
// Optionally, you can also delete the session cookie:
setcookie(session_name(), '', time() - 3600, '/');
Destroys a session.
session_start();
Starts a new session or resumes existing one.
session_id();
Returns or sets the current session ID.
session_name();
Returns or sets the session name.
session_status();
Returns session state (disabled, none, active).
session_unset();
Clears all session variables.
session_destroy();
Destroys session data on the server.
session_regenerate_id();
Creates a new session ID (useful for security).
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => 'yourdomain.com',
'secure' => true, // Only over HTTPS
'httponly' => true, // Prevent JS access
'samesite' => 'Strict' // Prevent CSRF
]);
It's a good security practice to set secure cookie options in php.ini or via code.
echo session_save_path();
By default, session data is stored as files (e.g., /tmp/sess_abc123). With this you can check where it is stored. You can also configure PHP to store sessions in a database, Redis, or memcached for scalability.
CREATE TABLE sessions (
id VARCHAR(128) NOT NULL PRIMARY KEY,
data TEXT NOT NULL,
timestamp INT NOT NULL
);
Create a table to hold session data.
id - The session ID (PHPSESSID)
data - Serialized session data
timestamp - Last access time (for cleanup)
<?php
class DBSessionHandler implements SessionHandlerInterface {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function open($savePath, $sessionName) {
return true;
}
public function close() {
return true;
}
public function read($id) {
$stmt = $this->pdo->prepare("SELECT data FROM sessions WHERE id = :id");
$stmt->execute(['id' => $id]);
$row = $stmt->fetchColumn();
return $row ? $row : '';
}
public function write($id, $data) {
$timestamp = time();
$stmt = $this->pdo->prepare("
REPLACE INTO sessions (id, data, timestamp)
VALUES (:id, :data, :timestamp)
");
return $stmt->execute([
'id' => $id,
'data' => $data,
'timestamp' => $timestamp
]);
}
public function destroy($id) {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE id = :id");
return $stmt->execute(['id' => $id]);
}
public function gc($maxlifetime) {
$old = time() - $maxlifetime;
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE timestamp < :old");
return $stmt->execute(['old' => $old]);
}
}
Create a custom session handler.
You can override PHP's default file-based handler by defining your own class that implements SessionHandlerInterface.
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$handler = new DBSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
// Now sessions will be saved in the database
$_SESSION['username'] = 'JohnDoe';
Register the handler before calling session_start().
; php.ini
session.gc_probability = 1
session.gc_divisor = 100
session.gc_maxlifetime = 1440
Garbage collection.
The gc() method above will remove expired sessions. You can also trigger it manually or rely on PHP's built-in probability settings.
With this on 1% of requests PHP runs garbage collection.
For security:
A cookie is a small piece of data stored on the user's browser and sent back to the server with each HTTP request.
They're often used for:
Remembering login sessions
Saving preferences (theme, language, etc.)
Tracking or analytics
Shopping cart persistence
setcookie(name, value, expire, path, domain, secure, httponly);
// Example:
setcookie("username", "JohnDoe", time() + 3600, "/");
This example creates a cookie named username with value JohnDoe that lasts for 1 hour. setcookie() must be called before any HTML output, just like header() or session_start().
if (isset($_COOKIE["username"])) {
echo "Welcome back, " . $_COOKIE["username"];
} else {
echo "Hello, new visitor!";
}
Cookies are automatically available in the $_COOKIE superglobal.
setcookie("username", "", time() - 3600, "/");
To delete a cookie, set its expiration time to a past value. This tells the browser to remove it.
| Parameter | Description |
|---|---|
name
|
The name of the cookie |
value |
The value to store |
expire |
When the cookie should expire (UNIX timestamp) |
path
|
The path on the server where the cookie is
available (/ for all)
|
domain |
The domain where the cookie is valid |
secure |
If true,
cookie is sent only over HTTPS
|
httponly |
If true,
cookie is not accessible via
JavaScript (prevents XSS)
|
samesite |
Restricts cross-site requests (Lax, Strict, or None)
|
setcookie(
"session_token",
"abc123xyz",
[
"expires" => time() + 3600,
"path" => "/",
"domain" => "example.com",
"secure" => true, // Only over HTTPS
"httponly" => true, // Not accessible via JS
"samesite" => "Strict"
]
);
For sensitive data, use secure cookie options.
// login.php
if ($_POST['remember']) {
setcookie("remember_me", $_POST['username'], time() + (86400 * 30), "/");
}
// welcome.php
if (isset($_COOKIE['remember_me'])) {
echo "Welcome back, " . $_COOKIE['remember_me'];
}
Example of remembering a user feature.
| Feature | Cookies | Sessions |
|---|---|---|
| Storage | On the client (browser) | On the server |
| Size limit | Usually ~4KB per cookie | Depends on server resources |
| Security | Can be read/edited by user | More secure (server-side) |
| Lifetime | Until expiry or browser close | Until session expires |
| Common use | Preferences, tracking | Authentication, user data |
| Extension | Description | Recommended |
|---|---|---|
| MySQLi | MySQL Improved, works only with MySQL | Simple and fast |
| PDO | PHP Data Objects, works with many databases | Flexible and modern |
<?php
$servername = "localhost";
$username = "root";
$password = "";
$database = "testdb";
$conn = new mysqli($servername, $username, $password, $database);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
echo "Connected successfully!";
$conn->close();
?>
Connecting with MySQLi.
<?php
$dsn = "mysql:host=localhost;dbname=testdb;charset=utf8mb4";
$user = "root";
$pass = "";
try {
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connected successfully!";
} catch (PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
?>
Connecting with PDO.
$result = $conn->query("SELECT * FROM users");
while ($row = $result->fetch_assoc()) {
echo $row["username"] . "<br>";
}
Executing MySQLi query.
$stmt = $pdo->query("SELECT * FROM users");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row["username"] . "<br>";
}
Executing PDO query.
$stmt = $conn->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$email = "user@example.com";
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo $row["username"];
}
Preventing SQL Injection with prepared statements with MySQLi.
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => 'user@example.com']);
$user = $stmt->fetch();
echo $user['username'];
Preventing SQL Injection with prepared statements with PDO. PDO automatically escapes parameters safely.
$conn->close();
Closing the connection with MySQLi.
$pdo = null;
Closing the connection with PDO.
5; // literal expression
$x = 10; // assignment expression
$y = $x + 5; // arithmetic expression
echo $x > $y; // comparison expression
An expression is anything that has a value. It can be a variable, a literal value, or a combination of values and operators. Every expression evaluates to a result.
| Operator | Description | Example | Result |
|---|---|---|---|
+
|
Addition | $a + $b |
Sum |
- |
Subtraction | $a - $b |
Difference |
* |
Multiplication | $a * $b |
Product |
/ |
Division | $a / $b |
Quotient |
% |
Modulus | $a % $b |
Remainder |
** |
Exponentiation | $a ** $b |
Power |
$a = 5;
$b = 2;
echo $a ** $b; // 25
Example of an arithmetic operator.
| Operator | Example | Equivalent |
|---|---|---|
= |
$x = 10 |
Assign 10 to x |
+= |
$x += 5 |
$x = $x + 5 |
-= |
$x -= 5 |
$x = $x - 5 |
*= |
$x *= 2 |
$x = $x * 2 |
/= |
$x /= 2 |
$x = $x / 2 |
.= |
$x .= "Hi" |
Concatenate strings |
$name = "John";
$name .= " Doe"; // John Doe
Example of an assignment operator.
| Operator | Description | Example | Result |
|---|---|---|---|
== |
Equal (value) | 5 == '5' |
true |
=== |
Identical (value + type) | 5 === '5' |
false |
!= or <> |
Not equal | 5 != 10 |
true |
!== |
Not identical | 5 !== '5' |
true |
> |
Greater than | 10 > 5 |
true |
< |
Less than | 2 < 3 |
true |
>= |
Greater or equal | 5 >= 5 |
true |
<= |
Less or equal | 3 <= 5 |
true |
<=> |
Spaceship (PHP 7+) | $a <=> $b |
-1, 0, 1 |
echo 10 <=> 20; // -1
echo 20 <=> 20; // 0
echo 30 <=> 20; // 1
Example of comparsion operators.
| Operator | Meaning | True when... | Example |
|---|---|---|---|
&& |
AND | both sides are true | $x > 0 && $y > 0 |
and |
AND (low precedence) | both sides are true | $a = ($x > 0 and $y > 0) |
! |
NOT | the condition is false | !($x == 10) |
xor |
XOR (exclusive OR) | exactly one is true | ($x == 5 xor $y == 10) |
|| |
OR | at least one is true | $a = ($x > 0 || $y > 0) |
or |
OR (low precedence) | at least one is true | $a = ($x > 0 or $y > 0) |
$age = 25;
if ($age > 18 && $age < 65) {
echo "Working age";
}
Example of a logical operator.
| Operator | Description |
|---|---|
. |
Concatenation |
.= |
Append string |
$a = "Hello ";
$b = "World!";
echo $a . $b; // Hello World!
Example of a string operator.
| Operator | Description | Example |
|---|---|---|
++$x |
Pre-increment | Increment before use |
$x++ |
Post-increment | Increment after use |
--$x |
Pre-decrement | Decrement before use |
$x-- |
Post-decrement | Decrement after use |
$x = 5;
echo ++$x; // 6
echo $x++; // 6 (then becomes 7)
Example of increment operators.
| Operator | Description |
|---|---|
+ |
Union of arrays |
== |
Equal (same key/value pairs) |
=== |
Identical (same order + type) |
!= |
Not equal |
<> |
Not equal |
!== |
Not identical |
$a = ["a" => 1, "b" => 2];
$b = ["c" => 3, "b" => 2];
print_r($a + $b); // ["a"=>1, "b"=>2, "c"=>3]
Example of an array operator.
$result = ($age >= 18) ? "Adult" : "Minor";
// PHP 7+ also allows:
$result = $value ?: "default"; // returns default if $value is false/null/empty
Example of a ternary operator. It's the short form of an if/else.
$username = $_GET["user"] ?? "Guest";
// Equivalent to:
$username = isset($_GET["user"]) ? $_GET["user"] : "Guest";
Null coalescing operator (??). Returns the first value that is not null.
$a = 10;
$b = &$a;// $b is a reference to $a
$b = 20;
echo $a; // 20 (because $b and $a reference the same value)
The reference operator (&) creates an alias for a variable, meaning both variables point to the same memory location. Both $a and $b now refer to the same variable in memory. If one changes, the other updates automatically.
function addOne(&$num) {
$num++;
}
$x = 5;
addOne($x);
echo $x; // 6
Example of passing by reference (in functions). Without &, $x would remain 5 because parameters are passed by value by default.
function &getCounter() {
static $count = 0;
return $count;
}
$ref = &getCounter();
$ref++;
echo getCounter(); // 1
Example of returning by reference.
class User {
public $name = "John";
public function greet() {
return "Hello, " . $this->name;
}
}
$user = new User();
echo $user->name; // John
echo $user->greet(); // Hello, John
Object operator (->). Used to access properties and methods of an object.
// Without it:
echo $user->profile->address; // Error if $user->profile is null
// With ?->:
echo $user?->profile?->address; // Returns null safely if any part is null
Nullsafe operator (?->) (PHP 8+). Prevents fatal errors when chaining properties/methods on a possibly null object.
class Math {
const PI = 3.14;
public static function square($n) {
return $n * $n;
}
}
echo Math::PI; // 3.14
echo Math::square(5); // 25
// Also used with:
parent::methodName();
self::property;
static::method(); // for late static binding
Scope resolution operator (::).
Used to access:
Static properties/methods
Class constants
Inherited members
// Creates an instance of a class:
$user = new User();
// You can even chain it immediately:
echo (new User())->greet();
Object instantiation operator (new).
if ($user instanceof User) {
echo "It's a User object!";
}
Instanceof opertor checks if an object belongs to a specific class or subclass.
$result = @file_get_contents("nonexistent.txt");
Error control operator (@). Suppresses error messages (not recommended for production use). In the example above, even if the file doesn't exist, no warning will be shown. Useful in controlled cases, but generally discouraged because it hides problems.
$output = `ls -l`;
echo $output;
Execution operator (` `) Executes shell commands directly. In this example it runs the ls -l command in the shell. Use carefully, it can pose serious security risks if input is not sanitized.
echo 1 <=> 2; // -1
echo 2 <=> 2; // 0
echo 3 <=> 2; // 1
// Often used in sorting functions:
usort($numbers, fn($a, $b) => $a <=> $b);
Combined comparison operator (<=>), a.k.a. spaceship operator
Returns:
-1 if left < right
0 if equal
1 if left > right
// Old way:
$title = ' PHP 8.5 Released ';
$slug = strtolower(
str_replace('.', '',
str_replace(' ', '-',
trim($title)
)
)
);
var_dump($slug); // string(15) "php-85-released"
// New way:
$title = ' PHP 8.5 Released ';
$slug = $title
|> trim(...)
|> (fn($str) => str_replace(' ', '-', $str))
|> (fn($str) => str_replace('.', '', $str))
|> strtolower(...);
var_dump($slug); // string(15) "php-85-released"
The pipe operator (PHP 8.5+) allows chaining function calls together without dealing with intermediary variables. This enables replacing many "nested calls" with a chain that can be read forwards, rather than inside-out.
// Enable all errors (for debugging)
error_reporting(E_ALL);
ini_set('display_errors', 1);
PHP can display or hide errors depending on configuration.
The above example shows:
Syntax errors
Warnings
Notices
Deprecated functions
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/php_errors.log');
This doesn't display errors, but logs them.
| Function | Description |
|---|---|
var_dump($var) |
Shows detailed info about a variable (type + value) |
print_r($var) |
Displays human-readable array/object structure |
var_export($var) |
Outputs valid PHP code representation |
debug_zval_dump($var) |
Shows reference and refcount info (advanced) |
gettype($var) |
Returns the variable’s type |
debug_backtrace() |
Shows the current call stack |
$user = ['name' => 'Alice', 'age' => 30];
var_dump($user);
// Output:
array(2) {
["name"]=>
string(5) "Alice"
["age"]=>
int(30)
}
print_r($user);
// Output:
Array
(
[name] => Alice
[age] => 30
)
An example of common debugging functions.
echo '<pre>';
var_dump($data);
echo '</pre>';
Makes var_dump() easier to read in HTML.
$data = getUserData();
var_dump($data);
die("Stopped for debugging...");
// Using a shorthand:
die(var_dump($data));
Sometimes, you want to stop execution and inspect what's happening.
function test1() { test2(); }
function test2() { debug_print_backtrace(); }
test1();
// Output:
#0 test2() called at [file.php:2]
#1 test1() called at [file.php:6]
Shows the stack trace, how PHP reached a certain point in your code.
error_log("User login failed for ID: $userId");
// Or:
file_put_contents('debug.log', "Checkpoint reached\n", FILE_APPEND);
Instead of printing to the browser, log messages to a file. Logging is safer and cleaner, especially in production.
assert($user !== null, 'User should not be null');
If the assertion fails, PHP triggers a warning or an exception (depending on configuration).
Xdebug is a PHP extension that provides:
It integrates with IDEs.
zend_extension="xdebug.so"
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.log="/var/log/xdebug.log"
; IDE connection
xdebug.client_host=127.0.0.1
xdebug.client_port=9003
After installing it you can enable it in php.ini. The above example is a common setup for IDE debugging. Make sure your IDE listens on port 9003.
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003
}
]
}
In VS Code, install the official PHP Debug extension (by xdebug.php-debug) and then add the code above to .vscode/launch.json.
xdebug.mode=profile
xdebug.output_dir="/tmp/xdebug_profiles"
This enables profiling.
The profiler shows which functions consume the most time and memory.
This generates .cachegrind files.
You can open them with:
QCacheGrind
Webgrind
Xdebug Profiler Viewer
It's a great way to find performance bottlenecks.
$lastLine = exec('ls -l', $output, $status);
echo "Last line: $lastLine\n";
echo "Exit code: $status\n";
print_r($output);
exec() executes a command and returns the last line of output.
$output - array of each line printed
$status - exit code (0 = success)
$output = shell_exec('date');
echo "The server time is: $output";
shell_exec() executes a command and returns the entire output as a string.
echo "<pre>";
system('ls -l');
echo "</pre>";
system() executes a command and outputs it directly to the browser.
header("Content-Type: image/png");
passthru("cat logo.png");
passthru() executes a command and passes raw binary output (for files, binaries). In this example the file is streamed directly to the browser without buffering.
$descriptorSpec = [
0 => ["pipe", "r"], // stdin
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"], // stderr
];
$process = proc_open('php -r "echo strtoupper(fgets(STDIN));"', $descriptorSpec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], "hello world\n");
fclose($pipes[0]);
echo "Output: " . stream_get_contents($pipes[1]);
fclose($pipes[1]);
$returnCode = proc_close($process);
echo "\nReturn code: $returnCode\n";
}
proc_open() opens a process with full control over input/output pipes. This example sends "hello world" to a PHP subprocess and reads "HELLO WORLD" from its output.
$handle = popen('python3 -c "import sys; print(sys.stdin.read().upper())"', 'w');
if ($handle) {
fwrite($handle, "hello from php");
pclose($handle);
}
popen() starts a command as a process and opens a pipe (a data stream) to it.
$command: the shell command to run
$mode: "r" to read the command's output, "w" to send input to it
It returns a file handle, which you can read/write using normal file functions like fgets(), fwrite(), fclose(), etc.
The example above uses popen() to send data to a Python script.
if (shell_exec("which git")) {
echo "Git is available!";
} else {
echo "Git not found!";
}
Tests if a command exists.
$user = $_GET['user'];
shell_exec("ls /home/$user");
Never do this! Executing shell commands can expose your system to serious security risks. If $user = ; rm -rf /, it could destroy the server.
$user = escapeshellarg($_GET['user']);
shell_exec("ls /home/$user");
Always sanitize or escape user input.
// php.ini:
disable_functions = exec, system, shell_exec, passthru, popen, proc_open
It's best to disable dangerous functions in production.
echo time();
// Example output: 1760748000
time() returns the current Unix timestamp (number of seconds since January 1, 1970 UTC).
echo date("Y-m-d H:i:s");
// Example output: 2025-11-02 20:45:31
date() formats a timestamp into a human-readable date/time string.
| Format | Meaning | Example |
|---|---|---|
Y |
4-digit year | 2025 |
y |
2-digit year | 25 |
m |
Month (01–12) | 11 |
d |
Day of the month | 03 |
H |
24-hour format | 22 |
h |
12-hour format | 10 |
i |
Minutes | 45 |
s |
Seconds | 31 |
a |
am / pm | pm |
l |
Full weekday name | Monday |
D |
Short weekday | Mon |
F |
Full month name | November |
$timestamp = 1609459200;
echo date("Y-m-d H:i:s", $timestamp);
// Output: 2021-01-01 00:00:00
You can pass a timestamp as the second parameter to date().
$timestamp = mktime(14, 30, 0, 12, 25, 2025);
echo date("Y-m-d H:i:s", $timestamp);
// Output: 2025-12-25 14:30:00
mktime() creates a Unix timestamp from specific date/time components.
echo strtotime("next Friday");
// e.g., 1731033600
echo date("Y-m-d", strtotime("tomorrow"));
echo date("Y-m-d", strtotime("+1 week"));
echo date("Y-m-d", strtotime("last Monday"));
strtotime() parses a human-readable date/time string into a Unix timestamp.
$date = new DateTime();
echo $date->format('Y-m-d H:i:s');
The DateTime class is the recommended way to handle date and time in PHP, especially for anything complex.
$date = new DateTime('2025-12-25 14:00:00');
echo $date->format('l, F j Y g:i A');
Creates a specific date.
$date->modify('+2 days');
echo $date->format('Y-m-d'); // adds two days
Modifies a date.
$d1 = new DateTime('2025-01-01');
$d2 = new DateTime('2025-11-03');
if ($d1 < $d2) echo "d1 is earlier";
Compares dates.
date_default_timezone_set('Europe/London');
echo date('Y-m-d H:i:s');
You can set or change the timezone globally or per object. The setting is global (affects all date() and time() calls).
$date = new DateTime('now', new DateTimeZone('America/New_York'));
echo $date->format('Y-m-d H:i:s');
Changes timezone per DateTime object.
echo date_default_timezone_get();
Gets current timezone.
$d1 = new DateTime('2025-01-01');
$d2 = new DateTime('2025-11-03');
$interval = $d1->diff($d2);
echo $interval->format('%m months, %d days');
// Output: 10 months, 2 days
You can calculate differences between two dates easily with diff().
$date = new DateTime('2025-11-03');
$date->add(new DateInterval('P1M')); // Add 1 month
echo $date->format('Y-m-d'); // 2025-12-03
You can use modify() or add() / sub() with DateInterval to add or subtract time.
Interval format syntax:
P - period, followed by numbers and units:
Y - years
M - months
D - days
T - time separator
H - hours
M - minutes
S - seconds
Example: "P1Y2M10DT2H30M" = 1 year, 2 months, 10 days, 2 hours, 30 minutes.
$start = microtime(true);
// some process...
usleep(100000); // 0.1 sec
$end = microtime(true);
echo "Elapsed time: " . ($end - $start) . " seconds";
If you need high precision timing (e.g., benchmarking).
When you implement the Iterator interface, you must define 5 methods that tell PHP how to iterate:
current() - Returns the current element value
key() - Returns the current key/index
next() - Moves to the next element
rewind() - Rewinds the iterator to the first element
valid() - Checks if the current position is valid
When you write:
foreach ($nums as $key => $value)
PHP internally executes this flow:
$nums->rewind()
While $nums->valid() is true:
$key = $nums->key()
$value = $nums->current()
loop body executes
$nums->next()
This pattern makes iterators extremely flexible for custom data structures.
class Numbers implements Iterator {
private $numbers = [10, 20, 30];
private $index = 0;
public function current() {
return $this->numbers[$this->index];
}
public function key() {
return $this->index;
}
public function next() {
$this->index++;
}
public function rewind() {
$this->index = 0;
}
public function valid() {
return isset($this->numbers[$this->index]);
}
}
$nums = new Numbers();
foreach ($nums as $key => $num) {
echo "Key: $key, Value: $num\n";
}
// Output:
Key: 0, Value: 10
Key: 1, Value: 20
Key: 2, Value: 30
An example of a custom iterator.
PHP's SPL (Standard PHP Library) provides a rich set of ready-to-use iterators:
ArrayIterator - Wraps arrays so they behave like objects
DirectoryIterator - Iterates over files in a directory
RecursiveIteratorIterator - Loops through nested structures
FilterIterator - Filters items dynamically during iteration
LimitIterator - Limits results to a subset
CachingIterator - Caches results of another iterator
EmptyIterator - An iterator that has no elements
CallbackFilterIterator - Filters using a callback function
$dir = new DirectoryIterator('.');
foreach ($dir as $file) {
if (!$file->isDot()) {
echo $file->getFilename() . "\n";
}
}
Example of DirectoryIterator. This example lists all files in the current directory.
class PhpFileFilter extends FilterIterator {
public function accept() {
return $this->current()->getExtension() === 'php';
}
}
$dir = new DirectoryIterator('.');
$phpFiles = new PhpFileFilter($dir);
foreach ($phpFiles as $file) {
echo $file->getFilename() . "\n";
}
Example of FilterIterator. This only lists .php files.
$dir = new RecursiveDirectoryIterator('.');
$it = new RecursiveIteratorIterator($dir);
foreach ($it as $file) {
echo $file->getPathname() . "\n";
}
You can use RecursiveIterator to loop through nested data structures or directories. This example lists all files and subdirectory contents recursively.
class Team implements IteratorAggregate {
private $members = ['Alice', 'Bob', 'Charlie'];
public function getIterator() {
return new ArrayIterator($this->members);
}
}
$team = new Team();
foreach ($team as $member) {
echo $member . "\n";
}
Instead of implementing all 5 methods yourself, you can use IteratorAggregate, which only needs one method. This way it's much simpler, the heavy lifting is done by ArrayIterator.
Iterators are useful when:
A generator is a special kind of function that can yield multiple values over time, instead of returning them all at once.
They provide a simple and memory-efficient way to implement iterators, without writing full classes or managing indexes manually.
They are basically lazy iterators:
They generate values on demand
Use almost no memory
Are ideal for large datasets, file I/O, or streaming
function countToThree() {
yield 1;
yield 2;
yield 3;
}
foreach (countToThree() as $number) {
echo $number . "\n";
}
// Output:
1
2
3
A generator function looks just like a normal function, but instead of using return, it uses yield.
When you call a generator function (like countToThree()), it doesn't execute immediately.
Instead, it returns a special Generator object that implements the Iterator interface.
PHP then executes it step by step inside the foreach loop, resuming at each yield.
That's what makes it:
Lazy (values produced only when needed)
Memory-efficient (no full array in memory)
Simple (no class boilerplate)
// Traditional way (returns an array):
function getNumbers() {
$nums = [];
for ($i = 1; $i <= 5; $i++) {
$nums[] = $i;
}
return $nums;
}
// Using a generator:
function getNumbers() {
for ($i = 1; $i <= 5; $i++) {
yield $i;
}
}
The traditional way creates a full array in memory. While with the generator now the numbers are produced one at a time. If you have 1,000,000 items, it uses almost no extra memory.
function animals() {
yield 'a' => 'Ant';
yield 'b' => 'Bat';
yield 'c' => 'Cat';
}
foreach (animals() as $key => $value) {
echo "$key => $value\n";
}
// Output:
a => Ant
b => Bat
c => Cat
You can assign both a key and a value.
function fruits() {
yield from ['Apple', 'Banana'];
yield from ['Cherry', 'Date'];
}
foreach (fruits() as $fruit) {
echo $fruit . "\n";
}
// Output:
Apple
Banana
Cherry
Date
Use yield from to delegate to another generator or iterable.
function sumNumbers($numbers) {
$sum = 0;
foreach ($numbers as $n) {
$sum += $n;
yield $sum;
}
return $sum;
}
$gen = sumNumbers([1, 2, 3]);
foreach ($gen as $partial) {
echo "Partial sum: $partial\n";
}
echo "Final sum: " . $gen->getReturn() . "\n";
// Output:
Partial sum: 1
Partial sum: 3
Partial sum: 6
Final sum: 6
Generators can also return a final value (since PHP 7).
function greeter() {
$name = yield "What's your name?";
yield "Hello, $name!";
}
$gen = greeter();
echo $gen->current() . "\n"; // "What's your name?"
echo $gen->send('Alice') . "\n"; // "Hello, Alice!"
Generators can even receive data. This example shows how generators can act like coroutines, pausing and resuming with data passed in.
The object returned by a generator implements the Iterator interface and has extra methods:
current() - Returns current yielded value
key() - Returns current key
next() - Resumes execution until next yield
valid() - Checks if generator is still running
send($value) - Sends a value into the generator
throw($exception) - Throws an exception inside the generator
getReturn() - Returns the final value (from return)
function readLines($filename) {
$handle = fopen($filename, 'r');
if (!$handle) return;
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
}
foreach (readLines('bigfile.txt') as $line) {
echo $line . "\n";
}
This is an example of reading a big file efficiently. It reads a 100MB file line-by-line using only a few KB of memory.
Composer is PHP's dependency manager, similar to:
npm for Node.js
pip for Python
maven for Java
It allows to:
Download and manage third-party libraries
Automatically handle dependencies
Generate an autoloader for classes
# On Linux/macOS:
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
# On Windows:
# Download installer from https://getcomposer.org/download/
# Once installed, check:
composer --version
You can install it globally.
| Command | Description |
|---|---|
composer init |
Create new composer.json |
composer install |
Install dependencies |
composer update |
Update dependencies |
composer require package/name |
Add new dependency |
composer remove package/name |
Remove a dependency |
composer dump-autoload |
Rebuild autoloader |
composer show |
List installed packages |
composer outdated |
Show packages with newer versions |
composer init
To start using Composer in a project you need to initialize it first.
This creates a composer.json file, the heart of every Composer project.
It will guide you through settings such as:
Project name
Description
Author
License
Dependencies
{
"name": "myapp/demo",
"description": "My PHP demo project",
"require": {
"monolog/monolog": "^3.0"
},
"autoload": {
"psr-4": {
"MyApp\\": "src/"
}
}
}
Example of a composer.json file.
This:
Installs the logging library Monolog
Defines PSR-4 autoloading for the namespace MyApp\
Maps that namespace to your local folder src/
composer install
This installs dependencies and creates:
A vendor/ directory (where all libraries go)
An auto-generated file: vendor/autoload.php
A composer.lock file (locks dependency versions)
// Structure of code:
project/
│
├── src/
│ └── Utils/
│ └── Helper.php
│
├── vendor/
│ └── autoload.php
│
└── composer.json
└── index.php
// src/Utils/Helper.php:
<?php
namespace MyApp\Utils;
class Helper {
public static function greet($name) {
return "Hello, $name!";
}
}
// index.php:
<?php
require 'vendor/autoload.php';
use MyApp\Utils\Helper;
echo Helper::greet('Alice');
// Output:
Hello, Alice!
Autoloading lets PHP load classes automatically when you use them. No need for dozens of require or include statements as Composer handles it automatically using PSR-4 autoloading rules.
// A class like:
namespace App\Models;
class User {}
// Should live in:
app/Models/User.php
PSR-4 (PHP Standard Recommendation 4) defines how file paths map to namespaces:
| Namespace | Folder Path | Example File |
|---|---|---|
MyApp\ |
src/ |
src/Utils/Helper.php |
App\Models\ |
app/Models/ |
app/Models/User.php |
spl_autoload_register(function($class) {
$file = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
if (file_exists($file)) {
require $file;
}
});
You can also define custom autoloaders manually if not using Composer. This automatically converts a namespace path (like App\Models\User) into a real file path (App/Models/User.php).
| Type | Description | Example |
|---|---|---|
| psr-4 | Modern standard, maps namespaces to directories | "MyApp\\": "src/" |
| psr-0 | Older version (deprecated) | "MyApp_": "src/" |
| classmap | Scans directories for all classes | "classmap": ["models/", "lib/"] |
| files | Loads specific files directly | "files": ["src/helpers.php"] |
"autoload": {
"psr-4": { "MyApp\\": "src/" },
"files": [ "src/helpers.php" ]
}
// Then run:
composer dump-autoload
This is an example of setting ap autoloading. This regenerates the autoloader file.
composer update
Updates every dependency.
composer update monolog/monolog
Updates a specific package.
// Install Monolog (popular logging library):
composer require monolog/monolog
// index.php:
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler('app.log', Logger::INFO));
$log->info('Application started.');
// Creates a log file app.log with a line like:
[2025-11-03T14:05:10.123456+00:00] app.INFO: Application started.
An example of installing a third party library.
function greet(string $name, int $age) {
echo "Hello $name, you are $age years old.";
}
greet("Alice", 25); // OK
greet(123, "Bob"); // TypeError
Type declarations tell PHP what type of data a function expects or returns. They make the code more reliable and easier to debug. Introduced gradually from PHP 5 - 8, they now support scalars, objects, arrays, unions, nullables, and more. PHP throws a TypeError if types don't match.
| Type | Description | Example |
|---|---|---|
int |
Integer | 42 |
float |
Floating point number | 3.14 |
string |
Text | "hello" |
bool |
Boolean | true / false |
array |
Array | [1, 2, 3] |
object |
Any object | new MyClass() |
callable |
Function or method callable | 'strlen', [obj, 'method'] |
iterable |
Arrays or Traversable objects | foreach ($x as $item) |
mixed |
Accepts any type | PHP 8+ |
null |
Null value | PHP 8.2+ (for union or standalone) |
function add(int $a, int $b): int {
return $a + $b;
}
echo add(3, 4); // 7
You can also specify the type a function should return. In this example if the function returns something else (like a string), PHP throws a TypeError.
class User {}
class Admin extends User {}
function getUserInfo(User $user) {
echo "User info retrieved.";
}
getUserInfo(new User()); // OK
getUserInfo(new Admin()); // (subtype OK)
getUserInfo("Not a user"); // TypeError
You can also require a function parameter to be an object of a certain class or to implement an interface. This supports polymorphism as subclasses are allowed.
function greet(?string $name) {
if ($name) echo "Hello, $name!";
else echo "Hello, stranger!";
}
greet("Alice"); // Hello, Alice!
greet(null); // Hello, stranger!
Nullable types allow either a specific type or null. Just prefix the type with a question mark (?).
function formatId(int|string $id): string {
return "ID: " . $id;
}
echo formatId(123); // ID: 123
echo formatId("ABC"); // ID: ABC
Union types (PHP 8+) allow multiple types by using the pipe (|) character. This replaces PHPDoc-like comments with real type safety.
interface Logger { public function log($msg); }
interface Storable { public function save(); }
class DataHandler implements Logger, Storable {
public function log($msg) {}
public function save() {}
}
function process(Logger&Storable $obj) {
$obj->log("Processing...");
$obj->save();
}
process(new DataHandler()); // Works
Intersection types (PHP 8.1+) require that a value implements multiple interfaces at the same time. In this example the object must implement both interfaces.
function debugValue(mixed $value): void {
var_dump($value);
}
debugValue("Hello");
debugValue(42);
debugValue(null);
The mixed type (PHP 8+) means "accept anything", including null. It's the default for untyped parameters, but using it explicitly improves clarity.
function logMessage(string $msg): void {
echo $msg;
}
Use void when a function doesn't return anything. Trying to return a value will cause a TypeError.
function fatal(string $msg): never {
throw new Exception($msg);
}
fatal("Something went wrong!");
The never type (PHP 8.1+) is used for functions that never return, because they either throw an exception or exit the script. It helps static analyzers understand code flow.
<?php
declare(strict_types=1);
function multiply(int $a, int $b): int {
return $a * $b;
}
multiply(3, 4); // OK
multiply("3", 4); // TypeError (strict mode)
By default, PHP performs weak typing. It may auto-convert values (e.g., string "5" to int 5). You can enforce strict typing at the top of your PHP file. Always place declare(strict_types=1); as the first line in a file (before any other code). This example throws an error, but it would run without the declare(strict_types=1); line.
class User {
public string $name;
public int $age;
}
$user = new User();
$user->name = "Alice"; // OK
$user->age = "25"; // TypeError
You can also type class properties (PHP 7.4+). Typed properties follow the same type rules as function parameters.
$sayHello = function($name) {
return "Hello, $name!";
};
echo $sayHello("Alice");
This is a basic example of an anonymous function. Anonymous functions (also called closures) are functions without a name, usually stored in variables or passed as arguments. They were introduced in PHP 5.3, and have become an essential part of modern PHP.
$message = "Hello";
$punctuation = "!";
$greeter = function($name) use ($message, $punctuation) {
return "$message, $name$punctuation";
};
echo $greeter("Bob"); // Hello, Bob!
PHP closures can use variables from the outer scope using the use keyword. In this example $message and $punctuation are copied into the closure. Modifying $message or $punctuation later does not affect the closure's copy.
$count = 0;
$increment = function() use (&$count) {
$count++;
};
$increment();
$increment();
echo $count; // 2
To allow the closure to modify the variable, use the reference operator (&). In this example the closure has access to the real variable therefore it can update it.
$numbers = [1, 2, 3, 4, 5];
$even = array_filter($numbers, function($n) {
return $n % 2 === 0;
});
print_r($even);
Many built-in PHP functions accept callbacks, including anonymous functions. In this example we are filtering an array using a callback function.
class User {
public $name = "Alice";
public function getGreeter() {
return function() {
return "Hello, " . $this->name;
};
}
}
$user = new User();
$greeter = $user->getGreeter();
echo $greeter(); // Hello, Alice
Inside an object, closures automatically have access to $this.
class A { public $x = "From A"; }
class B { public $x = "From B"; }
$closure = function() {
return $this->x;
};
$a = new A();
$b = new B();
$boundToA = $closure->bindTo($a, A::class);
$boundToB = $closure->bindTo($b, B::class);
echo $boundToA(); // From A
echo $boundToB(); // From B
It's possible to rebind a closure to a different object. You can bind a closure to any object. This changes what $this refers to.
// Long version:
$result = array_map(function($n) {
return $n * 2;
}, $numbers);
// With an arrow function:
$numbers = [1, 2, 3];
$result = array_map(fn($n) => $n * 2, $numbers);
Arrow functions are short closures with automatic variable capturing. They are are shorter, automatically use outer variables and are always by-value only (no by-reference).
$fn = function() {};
var_dump($fn instanceof Closure); // true
All anonymous functions are instances of the built-in Closure class.
This means closures can:
be stored in arrays
passed around
returned from functions
manipulated like objects
function multiplier($factor) {
return function($n) use ($factor) {
return $n * $factor;
};
}
$double = multiplier(2);
echo $double(5); // 10
$triple = multiplier(3);
echo $triple(5); // 15
Closures can be returned from functions (functional style). In this example we create a function that generates multipliers. This is a powerful technique borrowed from functional programming.
Attributes (PHP 8+) allow you to add structured metadata to classes, methods, properties, functions, parameters, and constants, similar to annotations in Java or decorators in Python. They are parsed by PHP and can be accessed at runtime using Reflection. They replace many older uses of docblocks, which were just comments and not understood by PHP itself.
#[MyAttribute]
class User {}
Attributes are written inside #[ ... ] brackets.
#[Route("/home", methods: ["GET", "POST"])]
function homeController() {}
They can also contain arguments.
#[Attribute] // Marks this class as an attribute definition
class Route {
public function __construct(
public string $path,
public array $methods
) {}
}
You can define your own attribute class by defining classes that represent attributes. #[Attribute] is a built-in attribute that tells PHP this class is allowed to be used as an attribute.
#[Route("/about", methods: ["GET"])]
function about() {}
This is how you apply an attribute.
$func = new ReflectionFunction('about');
$attributes = $func->getAttributes();
foreach ($attributes as $attr) {
$instance = $attr->newInstance();
echo $instance->path; // /about
print_r($instance->methods); // ["GET"]
}
This is how you can read attributes with reflection. getAttributes() returns objects describing attributes. newInstance() constructs the attribute class with the arguments passed earlier.
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
class Route {}
You can restrict what types of elements an attribute can be applied to.
Available targets:
TARGET_CLASS
TARGET_METHOD
TARGET_FUNCTION
TARGET_PROPERTY
TARGET_PARAMETER
TARGET_CLASS_CONSTANT
TARGET_ALL
#[Attribute(Attribute::IS_REPEATABLE)]
class Middleware {}
#[Middleware("auth")]
#[Middleware("admin")]
function dashboard() {}
By default, an attribute can only appear once on an element. But you can allow multiple uses.
// Define attributes:
#[Attribute]
class Route {
public function __construct(
public string $path,
public string $method
) {}
}
// Use in controller:
class UserController {
#[Route("/users", "GET")]
public function list() {
return ["Alice", "Bob"];
}
}
// Framework reads them:
$ref = new ReflectionClass(UserController::class);
foreach ($ref->getMethods() as $method) {
foreach ($method->getAttributes(Route::class) as $attr) {
$route = $attr->newInstance();
echo "{$route->method} -> {$route->path}\n";
}
}
This is how frameworks like Symfony, Laravel (future versions), and custom microframeworks use attributes to register routes.
class A { function test() {} }
class B extends A {
#[Override]
function test() {}
}
#[Override] (PHP 8.3+) ensures a method must override a parent method.
#[\NoDiscard]
function getPhpVersion(): string {
return 'PHP 8.5';
}
getPhpVersion();
// Warning:
The return value of function getPhpVersion() should either be used or intentionally ignored by casting it as (void)
By adding the #[\NoDiscard] attribute to a function, PHP will check whether the returned value is consumed and emit a warning if it is not. This allows improving the safety of APIs where the returned value is important, but it's easy to forget using the return value by accident. The associated (void) cast can be used to indicate that a value is intentionally unused.
$url = "https://api.example.com/data";
$response = file_get_contents($url);
$data = json_decode($response, true);
print_r($data);
Using file_get_contents() is the easiest way to perform a simple GET request, but it has no advanced features (headers, error handling).
$url = "https://api.example.com/login";
$options = [
"http" => [
"method" => "POST",
"header" => "Content-Type: application/json",
"content" => json_encode(["user" => "admin", "pass" => "1234"])
]
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
print_r(json_decode($response, true));
file_get_contents() can also send POST requests with stream contexts.
$curl = curl_init("https://api.example.com/users");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Sends a GET request using cURL.
$curl = curl_init("https://api.example.com/users");
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode([
"name" => "John",
"email" => "john@example.com"
])
]);
$response = curl_exec($curl);
curl_close($curl);
print_r(json_decode($response, true));
Sends a POST request using cURL.
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
You can set different HTTP methods for cURL.
curl_setopt($curl, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_TOKEN_HERE",
"Accept: application/json"
]);
This is how you can send headers such as authentication tokens with cURL.
curl_setopt($curl, CURLOPT_USERPWD, "username:password");
Basic authentication with cURL.
if ($response === false) {
die("cURL error: " . curl_error($curl));
}
Handles errors with cURL.
$data = json_decode($response, true);
Converts JSON to PHP array.
$json = json_encode($data);
// Common options:
json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
Converts PHP array to JSON.
// Install Guzzle:
composer require guzzlehttp/guzzle
// GET request:
use GuzzleHttp\Client;
$client = new Client();
$response = $client->get("https://api.example.com/data");
$data = json_decode($response->getBody(), true);
print_r($data);
// POST request:
$response = $client->post("https://api.example.com/login", [
'json' => ['user' => 'admin', 'pass' => '1234']
]);
A more modern approach is using Guzzle, which is a HTTP client library.
// Must be an email:
filter_var($email, FILTER_VALIDATE_EMAIL);
// Must be an integer between 1 and 10
filter_var($id, FILTER_VALIDATE_INT, array("options" => array("min_range"=>1, "max_range"=>10)));
// Cleans value
$name = filter_var($name, FILTER_SANITIZE_SPECIAL_CHARS);
Input validation and sanitization. User input should never be trusted. This is an example of validation and sanitization.
// Dangerous:
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
// Safe:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
SQL injection protection. Values are automatically escaped, this way SQL injection becomes impossible
// Dangerous:
echo $_GET['name'];
// Safe:
echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
Cross-Site Scripting (XSS). Injecting JavaScript into the HTML output can be prevented if you always sanitize data before displaying it on a webpage.
// Generate a session token:
$_SESSION['csrf'] = bin2hex(random_bytes(32));
// Include it in forms:
<input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">
// Verify it on submission:
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'])) {
die("CSRF attack detected");
}
Cross-Site Request Forgery (CSRF). Attackers trick users into making unauthorized requests, which can be prevented with a session tooken. Frameworks do this automatically.
// Hashing:
$hash = password_hash($password, PASSWORD_DEFAULT);
// Verifying:
if (password_verify($password, $hash)) {
echo "Valid password";
}
Password hashing.
Never store passwords in plain text.
Never use MD5, SHA1, SHA256, or any general hash function.
Instead use password_hash() and password_verify().
They:
Use bcrypt/argon2
Include salt
Automatically upgrade in the future
session_set_cookie_params([
'httponly' => true,
'secure' => true, // HTTPS only
'samesite' => 'Strict'
]);
Session security. Use session_regenerate_id(true) after login. Set secure cookie flags like in the example above. Do not store sensitive data in sessions. Destroy session completely on logout.
$f = $_FILES['file'];
if ($f['size'] > 1000000) die("Too large");
$ext = pathinfo($f['name'], PATHINFO_EXTENSION);
$allowed = ['jpg','png','pdf'];
if (!in_array($ext, $allowed)) die("Invalid type");
move_uploaded_file($f['tmp_name'], "/safe/path/$newName");
File uploads are dangerous.
So you must:
Validate MIME type
Validate extension
Rename the file
Store outside document root
Never execute uploaded files
Set correct permissions (0644)
// Dangerous:
include $_GET['page'];
// Safe:
$pages = ['home', 'about', 'contact'];
if (!in_array($_GET['page'], $pages)) {
die("Invalid page");
}
include "pages/{$_GET['page']}.php";
Preventing RFI/LFI (remote/local file inclusion attacks) by using a whitelist.
Output escaping:
HTML text - htmlspecialchars()
HTML attributes - htmlspecialchars()
JavaScript - json_encode()
URLs - urlencode()
disable_functions = exec,passthru,shell_exec,system
Disables dangerous functions.
display_errors = Off
log_errors = On
It's better to disable showing PHP errors in production.
echo memory_get_usage();
// e.g. 4023456
Returns the current memory usage in bytes.
echo memory_get_peak_usage();
Returns the peak memory usage since the script started. It's useful when profiling memory-heavy operations.
$start = microtime(true);
// some work here
$end = microtime(true);
echo "Execution time: " . ($end - $start) . " seconds";
microtime() returns the current Unix timestamp with microseconds.
$start = hrtime(true);
// work
$end = hrtime(true);
echo "Time: " . ($end - $start) . " ns";
hrtime() is a high-resolution timer available in newer PHP versions (nanoseconds precision). It's better than microtime() for benchmarking.
print_r(get_loaded_extensions());
Returns all loaded PHP extensions.
if (extension_loaded('curl')) {
echo "cURL available";
}
Checks if an extension is loaded.
echo ini_get("memory_limit");
Gets a php.ini configuration value.
ini_set("memory_limit", "512M");
Changes a php.ini setting at runtime.
echo php_uname();
Returns OS information.
echo phpversion();
Returns current PHP version.
print_r(sys_getloadavg());
Returns system load averages (1, 5, 15 minutes).
echo disk_free_space("/");
Returns the free space on the disk.
echo disk_total_space("/");
Returns the total space on the disk.
print_r(debug_backtrace());
Shows a stack trace.
debug_print_backtrace();
Prints the stack trace directly.
var_dump(error_get_last());
Gets the last error message.
error_reporting(E_ALL);
Gets or sets error reporting level.
print_r(opcache_get_status());
Shows OPcache stats, such as:
memory usage
hits, misses
cached scripts
print_r(opcache_get_configuration());
Shows OPcache config (from php.ini).
print_r(getrusage());
Returns resource usage for the current process, such as:
CPU time
memory faults
context switches
gc_mem_caches();
Clears the PHP memory manager cache. It's useful for memory measurement tests.
$startMemory = memory_get_usage();
$startTime = microtime(true);
// Some expensive work
$array = range(1, 1000000);
$endMemory = memory_get_usage();
$endTime = microtime(true);
echo "Memory used: " . ($endMemory - $startMemory) . " bytes\n";
echo "Time: " . ($endTime - $startTime) . " seconds\n";
This is an example of a simple performance monitor.
$status = 404;
$message = match ($status) {
200 => "OK",
301, 302 => "Redirect",
404 => "Not found",
500 => "Server error",
default => "Unknown status",
};
echo $message;
// Output:
Not found
A match expression (PHP 8+) compares a value against multiple conditions and returns a result.
It's similar to switch, but with important improvements:
It returns a value
It uses strict comparison (===)
No need for break
Each arm must produce a value
More concise and expressive
Supports multiple conditions per arm
match ("5") {
5 => "integer 5", // not matched
"5" => "string 5", // matched
};
With switch, "5" would match 5 because it uses ==. With match, we get "string 5".
$role = "manager";
$access = match ($role) {
"admin" => "full",
"editor" => "partial",
default => "none",
};
A match expression must cover all cases. Either by listing all values or using default.
$age = 25;
$category = match (true) {
$age < 12 => "child",
$age < 20 => "teen",
$age < 65 => "adult",
default => "senior",
};
echo $category; // "adult"
You can also use match (true) to replicate if/elseif logic.
$input = "y";
$result = match ($input) {
"y", "yes", "1" => "Accepted",
"n", "no", "0" => "Declined",
default => "Unknown",
};
You can even match multiple values.
$value = 3;
$result = match ($value) {
1 => strlen("abc"),
2 => rand(1, 10),
3 => strtoupper("hello"),
default => strtolower("WORLD"),
};
Each arm can return any expression.
echo match (10) {
1 => "one"
// missing default
};
// Error:
UnhandledMatchError
If there is no match and no default, PHP throws an exception. This prevents silent bugs.
$url = "https://example.com:8080/path/to/page?query=123#section";
$parts = parse_url($url);
print_r($parts);
// Output:
Array
(
[scheme] => https
[host] => example.com
[port] => 8080
[path] => /path/to/page
[query] => query=123
[fragment] => section
)
This function breaks a URL into components.
$host = parse_url($url, PHP_URL_HOST);
echo $host; // example.com
It's possible to extract only one component.
Available constants:
PHP_URL_SCHEME
PHP_URL_HOST
PHP_URL_PATH
PHP_URL_QUERY
PHP_URL_PORT
PHP_URL_FRAGMENT
PHP_URL_USER, PHP_URL_PASS
parse_str("page=1&sort=asc", $output);
print_r($output);
// Output:
Array
(
[page] => 1
[sort] => asc
)
The query string (?key=value&...) is not parsed into an array automatically, but PHP has a dedicated function for it.
$params = [
"search" => "php books",
"page" => 3
];
echo http_build_query($params);
// Output:
search=php+books&page=3
http_build_query() builds query strings.
$url = "https://example.com/page?x=1";
$parts = parse_url($url);
// Parse query into array
parse_str($parts['query'] ?? "", $query);
// Modify
$query['x'] = 42;
$query['y'] = 100;
// Rebuild query
$parts['query'] = http_build_query($query);
// Rebuild final URL
$newUrl = build_url($parts);
echo $newUrl;
// Output:
https://example.com/page?x=42&y=100
This is an example of modifying URLs.
use Uri\Rfc3986\Uri;
$uri = new Uri('https://php.net/releases/8.5/en.php');
var_dump($uri->getHost());
// string(7) "php.net"
The new always-available URI extension (PHP 8.5+) provides APIs to securely parse and modify URIs and URLs according to the RFC 3986 and the WHATWG URL standards.
Normally, PHP sends output to the browser immediately when it is echoed or printed.
But with output buffering, PHP stores the output in an internal buffer instead.
You can then:
modify it
clean or discard it
send it early or late
capture it as a string
This feature is useful for:
Sending headers even after producing output
Capturing HTML from a template file and return it as a string
Compressing or modify output (minify HTML, rewrite URLs, etc.)
Implementing a custom templating engine
Wraping output with a layout template
ob_start();
echo "Hello ";
echo "World!";
$output = ob_get_clean();
echo strtoupper($output); // HELLO WORLD!
ob_start() starts output buffering.
ob_start();
echo "Hello";
$data = ob_get_contents(); // "Hello"
ob_end_clean(); // clear buffer
ob_get_contents() returns the current buffer contents without clearing it.
$data = ob_get_contents();
ob_end_clean();
ob_get_clean() returns buffer contents and clears it. It's the equivalent to this example.
ob_start();
echo "SECRET";
ob_end_clean(); // Browser receives nothing
ob_end_clean() clears buffer without outputting anything.
ob_start();
echo "Hello";
ob_end_flush(); // sends "Hello"
ob_end_flush() sends buffer contents to the browser, then closes it.
ob_start();
echo "Loading...";
ob_flush();
flush(); // push to browser immediately
ob_flush() flushes (sends) buffer but keeps buffering active. It's usually paired with flush() to force actual output at server level.
ob_start();
echo "Hello";
ob_clean(); // clears
echo "World"; // only "World" remains
ob_end_flush(); // outputs "World"
ob_clean() clears buffer without closing it.
function minify($content) {
return str_replace(" ", "", $content);
}
ob_start("minify");
echo "Hello World"; // spaces removed
ob_end_flush();
// Output:
HelloWorld
You can pass a callback to ob_start() to automatically filter output.
Useful for:
HTML minifiers
Debug wrappers
Logging systems
Templating engines
ob_start();
echo "A";
ob_start();
echo "B";
$inner = ob_get_clean(); // "B"
echo "-$inner-"; // "A-B-"
ob_end_flush();
You can nest output buffers.
function render($file, $data = []) {
extract($data);
ob_start();
include $file;
return ob_get_clean();
}
// template.php
<h1>Hello <?= $name ?></h1>
// usage
echo render("template.php", ["name" => "Alice"]);
This example displays template rendering with buffering.
composer require --dev phpunit/phpunit
PHPUnit is the main testing framework for PHP. This is how you can install it.
// tests/MathTest.php
use PHPUnit\Framework\TestCase;
class MathTest extends TestCase {
public function testAdd() {
$this->assertEquals(4, 2 + 2);
}
}
Basic test example.
You can run it with:
vendor/bin/phpunit
public function testSum() {
// Arrange
$a = 5;
$b = 7;
// Act
$res = $a + $b;
// Assert
$this->assertSame(12, $res);
}
A typical test class has:
Arrange - prepare data or object.
Act - call the code to test.
Assert - verify output.
| Assertion | Meaning |
|---|---|
assertEquals($expected, $actual) |
Value equality |
assertSame($expected, $actual) |
Strict equality (===) |
assertTrue() / assertFalse() |
Boolean check |
assertNull() |
Checks null |
assertCount() |
Count elements |
assertInstanceOf() |
Object type check |
assertArrayHasKey() |
Array key existence |
assertStringContainsString() |
Check substring |
expectException() |
Assert an exception happens |
public function testException() {
$this->expectException(InvalidArgumentException::class);
throw new InvalidArgumentException();
}
This example shows testing for an exception.
public function testLoggerWasCalled() {
$logger = $this->createMock(Logger::class);
$logger->expects($this->once())
->method('log')
->with('test message');
$service = new MyService($logger);
$service->doSomething();
}
Mocks help test code that depends on something external.
This example shows how to use PHPUnit's mocking system.
This way we:
Avoid calling a real database / API / filesystem
Control the behavior of dependencies
Ensure correct interaction (method calls, arguments)
public function testUserIsSaved() {
$repo = new UserRepository($pdo);
$user = new User("Alice");
$repo->save($user);
$loaded = $repo->find("Alice");
$this->assertEquals($user->getName(), $loaded->getName());
}
Integration tests check how components interact. This example tests a repository with a real database. Integration tests are slower but provide more confidence.
$response = $client->request('GET', '/api/user/1');
$this->assertEquals(200, $response->getStatusCode());
In this example we test a REST API using Symfony HttpClient.
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src
Static analysis tools such as PHPStan don't run the code, but they scan it for errors.
They catch many bugs before runtime, as they can detect:
Type mismatches
Undefined variables
Incorrect method calls
Missing return types
vendor/bin/phpunit --coverage-html coverage/
PHPUnit supports code coverage with Xdebug, which is very useful for bigger projects.
This generates an HTML report showing:
Which lines are executed
Untested code
Test coverage per file/class
This document's home with other cheat sheets you might be interested in:
https://gitlab.com/davidvarga/it-cheat-sheets
Sources:
https://www.wikipedia.org/
https://stackoverflow.com/
https://dev.to/
https://www.w3schools.com/
https://www.php.net/
License:
GNU General Public License v3.0 or later