PHP Cheat Sheet
A complete PHP cheat sheet covering every function, operator and pattern worth keeping close, updated for PHP 8.4. Search it, filter by version, copy what you need.
Basics
Opening & Closing Tags
core<?php
echo "Hello";<?php
// no ?> at the end of this file<p><?= $name ?></p><?php if ($loggedIn): ?>
<p>Welcome back</p>
<?php endif; ?>Output
coreecho "Hello", " ", "world";$ok = print "Saved";printf("%s has %d points", "Ada", 42);$line = sprintf("%05.2f", 3.14159);print_r(['a' => 1, 'b' => 2]);var_dump(42, "42", true);var_export([1, 2]);ob_start();
echo "buffered";
$out = ob_get_clean();Comments
core// this line is ignored# also a single line comment/* spans
several lines *//**
* Adds two numbers.
*
* @param int $a
* @param int $b
* @return int
*/Variables
core$count = 10;
$name = "Ada";$Total = 5;
$total = 9; // a different variable$a = 1;
$b = &$a;
$b = 7; // $a is now 7$field = "email";
$$field = "a@b.co"; // sets $emailunset($temp);$page = $_GET['page'] ?? 1;Constants
coredefine("SITE", "example.com");
echo SITE;const MAX_UPLOAD = 5_000_000;if (defined('DEBUG')) {
// ...
}class Http {
public const OK = 200;
}
echo Http::OK;echo __LINE__, __FILE__, __DIR__;
echo __FUNCTION__, __CLASS__, __METHOD__;echo PHP_EOL; // newline for the platform
echo PHP_VERSION; // 8.3.6
echo PHP_INT_MAX; // 9223372036854775807Data Types
core$int = 42;
$float = 3.14;
$string = "text";
$bool = true;$array = [1, 2, 3];
$object = new stdClass();
$null = null;
$fn = fn($x) => $x * 2;echo gettype(1.5); // double
echo get_debug_type(1.5); // floatis_int($v); is_float($v);
is_string($v); is_bool($v);
is_array($v); is_object($v);
is_null($v); is_callable($v);
is_numeric($v);$budget = 1_250_000;$hex = 0xFF; // 255
$bin = 0b1010; // 10
$oct = 017; // 15, legacy leading zero$oct = 0o17; // 15Casting & Juggling
core$n = (int) "42abc"; // 42
$f = (float) "3.5"; // 3.5
$s = (string) 99; // "99"
$b = (bool) "0"; // false
$a = (array) $object;intval("0x1A", 16); // 26
floatval("1.5kg"); // 1.5
strval(true); // "1"
boolval([]); // falsefalse, 0, 0.0, "", "0", [], null0 == "foo"; // false in PHP 8, true before
"1" == "01"; // true, both numeric
"10" == "1e1"; // true0 === "0"; // false
1 === 1.0; // false$v = "7";
settype($v, "integer");Include & Require
coreinclude 'header.php';require 'config.php';require_once __DIR__ . '/bootstrap.php';require __DIR__ . '/vendor/autoload.php';// config.php
return ['db' => 'mysql'];
// index.php
$config = require 'config.php';Strings
Quoting
coreecho 'Total: $sum';$sum = 12;
echo "Total: $sum\n";echo "{$user['name']}'s cart";
echo "{$obj->price} EUR";$html = <<<HTML
<p>Hello $name</p>
HTML;$raw = <<<'SQL'
SELECT * FROM users WHERE id = $id
SQL; $t = <<<TXT
indented body
TXT;\n newline
\t tab
\\ backslash
\" double quote
\$ literal dollar
\u{1F600} unicode codepointJoining & Splitting
core$full = $first . ' ' . $last;$log = "start";
$log .= " -> done";echo implode(', ', ['a', 'b', 'c']);$parts = explode('/', 'usr/local/bin');explode(':', 'a:b:c', 2); // ['a', 'b:c']str_split('abcdef', 2); // ['ab', 'cd', 'ef']echo implode(PHP_EOL, $lines);Searching
coreif (str_contains($haystack, 'php')) { }str_starts_with($url, 'https://');
str_ends_with($file, '.php');if (strpos($s, 'x') !== false) { }stripos($s, 'PHP'); // ignores case
strrpos($s, '/'); // last occurrencesubstr_count('a-b-c', '-'); // 2strstr('user@mail.com', '@'); // @mail.com
strstr('user@mail.com', '@', true); // userSlicing & Trimming
coresubstr('abcdef', 1, 3); // bcdsubstr('abcdef', -2); // eftrim(" spaced "); // spacedltrim($s);
rtrim($s, "/\n");
trim($s, '"');substr_replace('abcdef', 'XY', 2, 2); // abXYef$card = str_repeat('*', 12) . substr($num, -4);Case & Padding
corestrtolower('ABC'); // abc
strtoupper('abc'); // ABC
ucfirst('hello'); // Hello
lcfirst('Hello'); // hello
ucwords('two words'); // Two Wordsstr_pad('7', 3, '0', STR_PAD_LEFT); // 007str_repeat('-', 20);strrev('abc'); // cba
str_shuffle('abc');echo wordwrap($text, 72, "\n", true);$short = mb_strimwidth($t, 0, 50, '...');Replacing
corestr_replace('cat', 'dog', 'a cat here');str_replace(['a', 'b'], ['1', '2'], $s);str_replace('x', 'y', $s, $count);str_ireplace('PHP', 'php', $s);strtr('hi', 'hi', 'HI'); // HI
strtr($s, ['{n}' => $name]); // templateecho nl2br("line one\nline two");Formatting Numbers & Text
corenumber_format(1234567.891, 2); // 1,234,567.89number_format(1234.5, 2, ',', '.'); // 1.234,50%s string %d integer %f float
%05d zero padded %.2f two decimals
%x hex %b binary %% literal %sprintf('%1$s-%1$s', 'a'); // a-avsprintf('%s is %d', ['Ada', 36]);printf("%-10s|%5d\n", $name, $qty);Multibyte & Encoding
coremb_strlen('café'); // 4
strlen('café'); // 5 bytesmb_substr($s, 0, 10);
mb_strtoupper($s);
mb_strpos($s, 'é');mb_str_split('café'); // ['c', 'a', 'f', 'é']mb_internal_encoding('UTF-8');mb_detect_encoding($s, ['UTF-8', 'ISO-8859-1']);
mb_convert_encoding($s, 'UTF-8', 'ISO-8859-1');echo htmlspecialchars($input, ENT_QUOTES, 'UTF-8');urlencode('a b&c'); // a+b%26c
rawurlencode('a b&c'); // a%20b%26cComparing
corestrcmp('a', 'b'); // negative
strcasecmp('A', 'a'); // 0strncmp($a, $b, 4);strnatcmp('img12', 'img2'); // positivehash_equals($knownToken, $userToken);similar_text($a, $b, $percent);
levenshtein('kitten', 'sitting'); // 3
soundex('Robert'); metaphone('Thompson');Arrays
Creating
core$nums = [1, 2, 3];$user = [
'name' => 'Ada',
'email' => 'ada@example.com',
];$rows = [
['id' => 1, 'tag' => 'php'],
['id' => 2, 'tag' => 'sql'],
];$list = [];
$list[] = 'first';range(1, 5); // [1,2,3,4,5]
range('a', 'e'); // ['a'..'e']
range(0, 10, 2); // [0,2,4,6,8,10]array_fill(0, 3, null);
array_fill_keys(['a', 'b'], 0);$config = [
'debug' => true,
'cache' => false,
];Reading & Writing
coreecho $user['name'];
echo $rows[0]['tag'];$role = $user['role'] ?? 'guest';array_key_exists('role', $user);
isset($user['role']); // false when the value is nullcount($nums);
count($rows, COUNT_RECURSIVE);if ($nums === []) { }
if (empty($nums)) { }$first = $nums[array_key_first($nums)];
$last = $nums[array_key_last($nums)];Adding & Removing
corearray_push($stack, 'a', 'b');
$top = array_pop($stack);array_unshift($queue, 'first');
$next = array_shift($queue);unset($user['password']);$clean = array_values($sparse);array_splice($a, 2, 1); // remove one
array_splice($a, 2, 0, ['new']); // insertarray_slice($a, 1, 3);
array_slice($a, -2); // last two
array_slice($a, 1, 3, true); // keep keysSearching
corein_array('2', $nums, true);$key = array_search('php', $tags, true);array_keys($user);
array_keys($scores, 100);array_column($rows, 'tag');
array_column($rows, 'tag', 'id'); // keyed by id$hit = array_find($rows, fn($r) => $r['id'] === 2);array_any($nums, fn($n) => $n > 10);
array_all($nums, fn($n) => $n > 0);Transforming
core$doubled = array_map(fn($n) => $n * 2, $nums);array_map(fn($a, $b) => $a . $b, $x, $y);$evens = array_filter($nums, fn($n) => $n % 2 === 0);array_filter($a, $fn, ARRAY_FILTER_USE_KEY);
array_filter($a, $fn, ARRAY_FILTER_USE_BOTH);$clean = array_filter($input);$total = array_reduce($items, fn($c, $i) => $c + $i['price'], 0);array_walk($a, function (&$v, $k) { $v = trim($v); });Combining
corearray_merge([1, 2], [3, 4]); // [1,2,3,4]array_merge(['a' => 1], ['a' => 9]); // ['a' => 9]['a' => 1] + ['a' => 9, 'b' => 2]; // a stays 1$all = [...$first, ...$second];$merged = [...$defaults, ...$overrides];array_combine(['a', 'b'], [1, 2]);array_merge_recursive($base, $override);
array_replace_recursive($base, $override);Sorting
coresort($a); // ascending
rsort($a); // descendingasort($a); // by value
arsort($a); // by value, descendingksort($a);
krsort($a);usort($rows, fn($a, $b) => $a['age'] <=> $b['age']);usort($rows, fn($a, $b) =>
[$a['dept'], $a['name']] <=> [$b['dept'], $b['name']]
);uasort($a, $cmp);
uksort($a, $cmp);natsort($files);
natcasesort($files);array_reverse($a);
array_reverse($a, true); // keep keys
shuffle($a);Sets & Comparison
corearray_unique($tags);array_diff($a, $b);
array_intersect($a, $b);array_diff_key($a, $b);
array_intersect_key($a, $b);array_diff_assoc($a, $b);
array_intersect_assoc($a, $b);array_flip(['a' => 1, 'b' => 2]); // [1 => 'a', 2 => 'b']array_is_list([1, 2, 3]); // trueMaths & Chunking
corearray_sum([1, 2, 3]); // 6
array_product([2, 3, 4]); // 24max($nums); min($nums);
max(3, 9, 5); // 9array_chunk($rows, 3);
array_chunk($rows, 3, true); // preserve keysarray_pad([1, 2], 4, 0); // [1,2,0,0]$key = array_rand($a);
$keys = array_rand($a, 2);array_count_values(['a', 'b', 'a']); // ['a' => 2, 'b' => 1]Destructuring
core[$x, $y] = [10, 20];[, , $third] = $parts;['name' => $name, 'email' => $mail] = $user;foreach ($rows as ['id' => $id, 'tag' => $tag]) {
echo "$id: $tag";
}[$a, $b] = [$b, $a];$data = compact('name', 'email');
extract(['name' => 'Ada']); // creates $name[[$a, $b], [$c, $d]] = [[1, 2], [3, 4]];SPL & Iterators
core$bag = new ArrayObject(['a' => 1]);
$bag['b'] = 2;
foreach ($bag as $k => $v) { }
count($bag);$stack = new SplStack();
$stack->push('a');
$stack->push('b');
echo $stack->pop();$queue = new SplQueue();
$queue->enqueue('first');
$queue->enqueue('second');
echo $queue->dequeue();$seen = new SplObjectStorage();
$seen->attach($node);
if ($seen->contains($node)) { }
$seen->detach($node);$grid = new SplFixedArray(1000);
$grid[0] = 'x';$cache = new WeakMap();
$cache[$object] = $expensiveResult;
// entry disappears when $object is collected$q = new SplPriorityQueue();
$q->insert('low', 1);
$q->insert('urgent', 10);
echo $q->extract();$all = iterator_to_array(counter(3));
$all = iterator_to_array($gen, false); // discard keysarray_multisort($scores, SORT_DESC, $names);
// $names is reordered to match $scoresclass Cart implements Countable, IteratorAggregate
{
private array $lines = [];
public function count(): int {
return count($this->lines);
}
public function getIterator(): ArrayIterator {
return new ArrayIterator($this->lines);
}
}class Config implements ArrayAccess
{
public function offsetExists(mixed $k): bool { }
public function offsetGet(mixed $k): mixed { }
public function offsetSet(mixed $k, mixed $v): void { }
public function offsetUnset(mixed $k): void { }
}class Slug implements Stringable {
public function __toString(): string {
return $this->value;
}
}Control Flow
Conditionals
coreif ($score > 90) {
$grade = 'A';
} elseif ($score > 80) {
$grade = 'B';
} else {
$grade = 'C';
}<?php if ($user): ?>
<p>Hi <?= $user->name ?></p>
<?php else: ?>
<a href="/login">Sign in</a>
<?php endif; ?>$label = $count === 1 ? 'item' : 'items';$title = $input ?: 'Untitled';$limit = $options['limit'] ?? 20;$config['cache'] ??= true;$city = $user?->address?->city;Match & Switch
core$name = match ($code) {
200, 201 => 'Success',
404 => 'Not found',
default => 'Unknown',
};$band = match (true) {
$n < 10 => 'low',
$n < 100 => 'mid',
default => 'high',
};// UnhandledMatchError when no arm matches
$x = match ($v) { 1 => 'one' };switch ($role) {
case 'admin':
$level = 3;
break;
case 'editor':
case 'author':
$level = 2;
break;
default:
$level = 1;
}<?php switch ($tab): case 'home': ?>
Home
<?php break; endswitch; ?>Loops
corefor ($i = 0; $i < 10; $i++) {
echo $i;
}foreach ($items as $item) {
echo $item;
}foreach ($user as $field => $value) {
echo "$field: $value";
}foreach ($prices as &$price) {
$price *= 1.2;
}
unset($price);while ($row = $stmt->fetch()) {
process($row);
}do {
$attempt++;
} while ($attempt < 3);<?php foreach ($rows as $row): ?>
<li><?= $row['name'] ?></li>
<?php endforeach; ?>Loop Control
coreforeach ($rows as $row) {
if ($row['id'] === $target) break;
}foreach ($rows as $row) {
if (!$row['active']) continue;
render($row);
}foreach ($grid as $line) {
foreach ($line as $cell) {
if ($cell === 'x') break 2;
}
}foreach ($rows as $i => $row) {
echo ($i + 1) . '. ' . $row['name'];
}$class = $i % 2 === 0 ? 'even' : 'odd';Operators
core+ - * / % **
intdiv(7, 2); // 3, integer division
7 % 3; // 1
2 ** 10; // 1024$a += 5; $a -= 5;
$a *= 2; $a /= 2;
$a %= 3; $a **= 2;
$s .= 'more';== equal after juggling
=== identical, value and type
!= <> not equal
!== not identical
< > <= >=1 <=> 2; // -1
2 <=> 2; // 0
3 <=> 2; // 1&& and || or ! not
xor
// and / or have lower precedence than = & | ^ ~ << >>
$flags | self::READ;
$flags & self::WRITE;$v = @$undefined; // hides the warningGenerators
corefunction counter(int $max): Generator {
for ($i = 1; $i <= $max; $i++) {
yield $i;
}
}
foreach (counter(3) as $n) echo $n;function lines(string $path): Generator {
$fh = fopen($path, 'r');
while (($line = fgets($fh)) !== false) {
yield rtrim($line);
}
fclose($fh);
}yield $id => $row;function all(): Generator {
yield from first();
yield from second();
}$gen = process();
foreach ($gen as $item) { }
$summary = $gen->getReturn();$gen->send('resume value');Functions
Declaring
corefunction add(int $a, int $b): int {
return $a + $b;
}function greet(string $name, string $greeting = 'Hello'): string {
return "$greeting, $name";
}function find(?int $id): ?User { }function parse(int|string $input): array { }function save(Countable&Iterator $c): void { }function log(string $msg): void {
file_put_contents('app.log', $msg, FILE_APPEND);
}function fail(string $m): never {
throw new RuntimeException($m);
}declare(strict_types=1);Arguments
coresetCookie(name: 'session', httpOnly: true, secure: true);function sum(int ...$nums): int {
return array_sum($nums);
}
sum(1, 2, 3); // 6$args = [1, 2, 3];
sum(...$args);function addOne(array &$list): void {
$list[] = 1;
}function counter(): int {
static $n = 0;
return ++$n;
}$all = func_get_args();Anonymous Functions
core$double = function (int $n): int {
return $n * 2;
};
echo $double(4);$tax = 0.2;
$withTax = function ($p) use ($tax) {
return $p * (1 + $tax);
};$total = 0;
$add = function ($n) use (&$total) {
$total += $n;
};$withTax = fn($p) => $p * (1 + $tax);usort($rows, fn($a, $b) => $a['n'] <=> $b['n']);$config = (function () {
return require 'config.php';
})();Callables
corearray_map('strtoupper', $words);usort($rows, [$sorter, 'compare']);
usort($rows, ['SortHelper', 'compare']); // static$fn = strlen(...);
$fn = $obj->handle(...);
$fn = Sorter::compare(...);$bound = Closure::bind($fn, $object, Account::class);call_user_func($fn, $arg);
call_user_func_array($fn, $args);class Multiplier {
public function __invoke(int $n): int {
return $n * 3;
}
}
$m = new Multiplier();
echo $m(4);Recursion & Scope
corefunction factorial(int $n): int {
return $n <= 1 ? 1 : $n * factorial($n - 1);
}function flatten(array $tree): array {
$out = [];
foreach ($tree as $node) {
$out[] = $node['name'];
$out = array_merge($out, flatten($node['children'] ?? []));
}
return $out;
}$x = 5;
function show() {
// $x is not available here
}function show() {
global $config; // works, but hard to test
}if (function_exists('bcadd')) { }Fibers
modern$fiber = new Fiber(function (string $start): string {
$received = Fiber::suspend($start . ' paused');
return $received . ' finished';
});$paused = $fiber->start('job'); // 'job paused'
$result = $fiber->resume('job'); // returns from start
echo $fiber->getReturn();$fiber->isStarted();
$fiber->isSuspended();
$fiber->isRunning();
$fiber->isTerminated();// ReactPHP, Amp and Swoole build their
// schedulers on top of Fiber// Fiber : suspends the whole call stack
// Generator: suspends one functionClasses & OOP
Classes & Objects
coreclass User {
public string $name;
public int $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
}
$u = new User('Ada', 36);class User {
public function __construct(
public string $name,
public int $age = 0,
) {}
}public // anywhere
protected // this class and children
private // this class onlyecho $u->name;
$u->rename('Grace');class Counter {
public static int $count = 0;
public static function bump(): void {
static::$count++;
}
}
Counter::bump();class Money {
public function __construct(
public readonly int $amount,
) {}
}readonly class Point {
public function __construct(
public float $x,
public float $y,
) {}
}$copy = clone $order;
public function __clone(): void {
$this->lines = array_map(fn($l) => clone $l, $this->lines);
}Inheritance
coreclass Admin extends User {
public function __construct(string $name) {
parent::__construct($name, 0);
}
}abstract class Shape {
abstract public function area(): float;
public function describe(): string {
return static::class . ': ' . $this->area();
}
}final class Uuid { }
class Base {
final public function id(): string { }
}class Model {
public static function create(): static {
return new static();
}
}if ($obj instanceof Shape) { }
get_class($obj);
$obj::class;Interfaces & Traits
coreinterface Payable {
public function total(): float;
}
class Invoice implements Payable {
public function total(): float { return 99.0; }
}class Cart implements Countable, IteratorAggregate { }interface Status {
const ACTIVE = 'active';
}
interface Admin extends Status { }trait Timestamps {
public ?string $createdAt = null;
public function touch(): void {
$this->createdAt = date('c');
}
}
class Post {
use Timestamps;
}class Report {
use A, B {
A::render insteadof B;
B::render as renderAlt;
}
}trait Sluggable {
abstract public function title(): string;
}Enums
modernenum Status {
case Draft;
case Published;
case Archived;
}
$s = Status::Draft;enum Role: string {
case Admin = 'admin';
case Editor = 'editor';
}
echo Role::Admin->value;Role::from('admin'); // throws if unknown
Role::tryFrom('nope'); // null if unknownforeach (Role::cases() as $case) {
echo $case->name, '=', $case->value;
}enum Role: string {
case Admin = 'admin';
public function label(): string {
return match ($this) {
Role::Admin => 'Administrator',
};
}
}interface HasColour { public function colour(): string; }
enum Level: int implements HasColour {
case Low = 1;
public function colour(): string { return 'green'; }
}Magic Methods
corepublic function __construct() { }
public function __destruct() { }public function __get(string $name): mixed {
return $this->data[$name] ?? null;
}
public function __set(string $name, mixed $value): void {
$this->data[$name] = $value;
}public function __isset(string $n): bool {
return isset($this->data[$n]);
}
public function __unset(string $n): void {
unset($this->data[$n]);
}public function __call(string $m, array $args): mixed { }
public static function __callStatic(string $m, array $args): mixed { }public function __toString(): string {
return $this->name;
}
echo $user; // calls __toStringpublic function __invoke(mixed $arg): mixed { }public function __serialize(): array {
return ['id' => $this->id];
}Namespaces & Autoloading
corenamespace App\Models;
class User { }use App\Models\User;
use App\Models\Post as BlogPost;use App\Models\{User, Post, Comment};use function App\Helpers\slugify;
use const App\Config\VERSION;$date = new \DateTimeImmutable();echo User::class; // App\Models\User{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}spl_autoload_register(function (string $class): void {
$path = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php';
if (is_file($path)) require $path;
});Newer Class Features
modernclass Service {
public function __construct(
private Logger $logger = new NullLogger(),
) {}
}#[Route('/users', methods: ['GET'])]
public function index(): Response { }$r = new ReflectionMethod($obj, 'index');
foreach ($r->getAttributes(Route::class) as $attr) {
$route = $attr->newInstance();
}class Temp {
public float $celsius = 0;
public float $fahrenheit {
get => $this->celsius * 9 / 5 + 32;
set => $this->celsius = ($value - 32) * 5 / 9;
}
}class Order {
public private(set) string $status = 'new';
}$name = new User('Ada')->name();Web & Requests
Superglobals
core$page = (int) ($_GET['page'] ?? 1);$email = trim($_POST['email'] ?? '');$_SERVER['REQUEST_METHOD']; // GET, POST
$_SERVER['REQUEST_URI']; // /users?page=2
$_SERVER['HTTP_HOST'];
$_SERVER['REMOTE_ADDR'];
$_SERVER['HTTP_USER_AGENT'];
$_SERVER['SCRIPT_NAME'];$_FILES['avatar']['name'];
$_FILES['avatar']['tmp_name'];
$_FILES['avatar']['size'];
$_FILES['avatar']['error'];$_COOKIE['theme'];
$_SESSION['user_id'];$_ENV['APP_ENV'];
getenv('DATABASE_URL');$_REQUEST['id']; // mixes GET, POST and COOKIEReading Input Safely
coreif ($_SERVER['REQUEST_METHOD'] === 'POST') {
handleForm();
}$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
http_response_code(400);
exit('Bad id');
}filter_var($v, FILTER_VALIDATE_EMAIL);
filter_var($v, FILTER_VALIDATE_URL);
filter_var($v, FILTER_VALIDATE_INT);
filter_var($v, FILTER_VALIDATE_FLOAT);
filter_var($v, FILTER_VALIDATE_IP);
filter_var($v, FILTER_VALIDATE_BOOLEAN);$raw = file_get_contents('php://input');
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);parse_str('a=1&b=2', $out);echo http_build_query(['page' => 2, 'q' => 'php']);$parts = parse_url('https://a.com/p?x=1');
// scheme, host, path, queryHeaders & Responses
coreheader('Content-Type: application/json; charset=utf-8');header('Location: /dashboard', true, 302);
exit;http_response_code(404);header('Content-Type: application/json');
echo json_encode(['ok' => true], JSON_THROW_ON_ERROR);
exit;header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="report.csv"');
readfile($path);if (!headers_sent()) {
header('X-Frame-Options: DENY');
}header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Content-Security-Policy: default-src \'self\'');Sessions
coresession_start();
$_SESSION['user_id'] = $user->id;$uid = $_SESSION['user_id'] ?? null;session_start([
'cookie_httponly' => true,
'cookie_secure' => true,
'cookie_samesite' => 'Lax',
]);session_regenerate_id(true);$_SESSION = [];
session_destroy();if (session_status() === PHP_SESSION_ACTIVE) { }Cookies
coresetcookie('theme', 'dark', [
'expires' => time() + 86400 * 30,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);$theme = $_COOKIE['theme'] ?? 'light';setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);File Uploads
core$f = $_FILES['avatar'] ?? null;
if (!$f || $f['error'] !== UPLOAD_ERR_OK) {
exit('Upload failed');
}
if ($f['size'] > 2_000_000) {
exit('File too large');
}$mime = mime_content_type($f['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/webp'];
if (!in_array($mime, $allowed, true)) {
exit('Unsupported type');
}$ext = pathinfo($f['name'], PATHINFO_EXTENSION);
$name = bin2hex(random_bytes(16)) . '.' . strtolower($ext);
move_uploaded_file($f['tmp_name'], __DIR__ . "/uploads/$name");foreach ($_FILES['docs']['tmp_name'] as $i => $tmp) {
$name = $_FILES['docs']['name'][$i];
}UPLOAD_ERR_OK // 0
UPLOAD_ERR_INI_SIZE // 1, larger than upload_max_filesize
UPLOAD_ERR_FORM_SIZE // 2
UPLOAD_ERR_PARTIAL // 3
UPLOAD_ERR_NO_FILE // 4HTTP Requests Out
core$body = file_get_contents('https://api.example.com/items');$ctx = stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode(['name' => 'Ada']),
]]);
$body = file_get_contents($url, false, $ctx);$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);if (curl_errno($ch)) {
throw new RuntimeException(curl_error($ch));
}Files & JSON
Reading
core$text = file_get_contents(__DIR__ . '/notes.txt');$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);$fh = fopen($path, 'r');
while (($line = fgets($fh)) !== false) {
process($line);
}
fclose($fh);readfile($path);$fh = fopen($path, 'rb');
$chunk = fread($fh, 8192);if (is_readable($path)) { }Writing
corefile_put_contents($path, $content);file_put_contents($log, "$line\n", FILE_APPEND | LOCK_EX);$fh = fopen($path, 'w');
fwrite($fh, $data);
fclose($fh);'r' read only
'w' write, truncates or creates
'a' append, creates if missing
'x' create, fails if it exists
'r+' read and write
'b' binary safe, add to any mode$fh = fopen($path, 'c+');
if (flock($fh, LOCK_EX)) {
ftruncate($fh, 0);
fwrite($fh, $data);
flock($fh, LOCK_UN);
}
fclose($fh);$tmp = $path . '.tmp';
file_put_contents($tmp, $data);
rename($tmp, $path);Paths & Metadata
core$root = __DIR__;
require __DIR__ . '/../config.php';pathinfo('/var/www/app.min.js');
// dirname, basename, extension, filenamebasename($path); // app.min.js
basename($path, '.js'); // app.min
dirname($path); // /var/www$real = realpath('../uploads');file_exists($p);
is_file($p);
is_dir($p);
is_writable($p);filesize($p);
date('Y-m-d', filemtime($p));disk_free_space('/');Directories
coreif (!is_dir($dir)) {
mkdir($dir, 0755, true);
}$items = array_diff(scandir($dir), ['.', '..']);foreach (glob($dir . '/*.php') as $file) { }
glob($dir . '/{*.jpg,*.png}', GLOB_BRACE);$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($it as $file) {
echo $file->getPathname();
}unlink($file);
rmdir($emptyDir);copy($from, $to);
rename($from, $to);$tmp = tempnam(sys_get_temp_dir(), 'rep');
$dir = sys_get_temp_dir();JSON
coreecho json_encode(['id' => 1, 'ok' => true]);json_encode($data,
JSON_PRETTY_PRINT
| JSON_UNESCAPED_SLASHES
| JSON_UNESCAPED_UNICODE
);$data = json_decode($json, true);$obj = json_decode($json);
echo $obj->name;try {
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
// handle it
}if (json_last_error() !== JSON_ERROR_NONE) {
echo json_last_error_msg();
}$data = json_decode(file_get_contents($f), true);
file_put_contents($f, json_encode($data, JSON_PRETTY_PRINT));class Point implements JsonSerializable {
public function jsonSerialize(): array {
return ['x' => $this->x, 'y' => $this->y];
}
}CSV
core$fh = fopen($path, 'r');
$head = fgetcsv($fh);
while (($row = fgetcsv($fh)) !== false) {
$record = array_combine($head, $row);
}
fclose($fh);$fh = fopen($path, 'w');
fputcsv($fh, ['id', 'name']);
foreach ($rows as $row) {
fputcsv($fh, $row);
}
fclose($fh);header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="export.csv"');
$out = fopen('php://output', 'w');
fputcsv($out, ['id', 'name']);fgetcsv($fh, 0, ';', '"');$fields = str_getcsv($line);Streams & Wrappers
corephp://input raw request body
php://output write straight to the browser
php://memory in memory temp stream
php://temp memory, spills to disk when large
php://stdin command line input
data:// inline data
file:// http:// ftp:// zip:// phar://$fh = fopen('php://memory', 'r+');
fwrite($fh, $csv);
rewind($fh);
echo stream_get_contents($fh);stream_copy_to_stream($source, $dest);$ctx = stream_context_create([
'http' => ['timeout' => 5, 'ignore_errors' => true],
]);stream_filter_append($fh, 'string.toupper');
stream_get_filters();$lines = file('compress.zlib://big.log.gz');allow_url_fopen = On // needed by file_get_contents on URLs
allow_url_include = Off // never turn this onDatabase
PDO Connection
core$pdo = new PDO(
'mysql:host=localhost;dbname=app;charset=utf8mb4',
$user,
$pass,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);mysql:host=127.0.0.1;dbname=app;charset=utf8mb4
pgsql:host=127.0.0.1;dbname=app
sqlite:/var/data/app.sqlite
sqlite::memory:try {
$pdo = new PDO($dsn, $user, $pass, $opts);
} catch (PDOException $e) {
error_log($e->getMessage());
exit('Database unavailable');
}Queries
core$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch();$stmt = $pdo->prepare(
'SELECT * FROM posts WHERE status = :status AND author_id = :author'
);
$stmt->execute(['status' => 'live', 'author' => $id]);$rows = $pdo->prepare('SELECT * FROM tags');
$rows->execute();
$all = $rows->fetchAll();$stmt = $pdo->prepare('SELECT COUNT(*) FROM users');
$stmt->execute();
$count = (int) $stmt->fetchColumn();foreach ($pdo->query('SELECT id, name FROM users') as $row) {
echo $row['name'];
}$stmt = $pdo->prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
);
$stmt->execute([$name, $email]);
$id = (int) $pdo->lastInsertId();$stmt = $pdo->prepare('DELETE FROM sessions WHERE expires < ?');
$stmt->execute([time()]);
echo $stmt->rowCount();Fetch Modes
core$stmt->fetch(PDO::FETCH_ASSOC);$row = $stmt->fetch(PDO::FETCH_OBJ);
echo $row->name;$stmt->setFetchMode(PDO::FETCH_CLASS, User::class);
$user = $stmt->fetch();$ids = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);$map = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);$grouped = $stmt->fetchAll(PDO::FETCH_GROUP);Binding & Types
core$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);$stmt->bindParam(':id', $id, PDO::PARAM_INT);PDO::PARAM_STR
PDO::PARAM_INT
PDO::PARAM_BOOL
PDO::PARAM_NULL
PDO::PARAM_LOB$stmt = $pdo->prepare('SELECT * FROM p LIMIT :n');
$stmt->bindValue(':n', $n, PDO::PARAM_INT);
$stmt->execute();$in = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM t WHERE id IN ($in)");
$stmt->execute($ids);$term = '%' . str_replace(['%', '_'], ['\%', '\_'], $q) . '%';
$stmt->execute([$term]);Transactions
core$pdo->beginTransaction();
try {
$pdo->prepare('UPDATE accounts SET bal = bal - ? WHERE id = ?')
->execute([$amount, $from]);
$pdo->prepare('UPDATE accounts SET bal = bal + ? WHERE id = ?')
->execute([$amount, $to]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}if ($pdo->inTransaction()) {
$pdo->rollBack();
}$pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO logs (msg) VALUES (?)');
foreach ($messages as $m) {
$stmt->execute([$m]);
}
$pdo->commit();MySQLi
coremysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$db = new mysqli('localhost', $user, $pass, 'app');
$db->set_charset('utf8mb4');$stmt = $db->prepare('SELECT name FROM users WHERE id = ?');
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();i integer
d double
s string
b blobwhile ($row = $result->fetch_assoc()) {
echo $row['name'];
}$db->insert_id;
$db->affected_rows;$stmt->close();
$db->close();Errors
Try, Catch, Finally
coretry {
$result = risky();
} catch (RuntimeException $e) {
error_log($e->getMessage());
$result = null;
}try {
send();
} catch (NetworkException | TimeoutException $e) {
retry();
}try {
$fh = fopen($path, 'r');
return process($fh);
} finally {
if (isset($fh)) fclose($fh);
}try {
boot();
} catch (Throwable $e) {
http_response_code(500);
}try {
parse($v);
} catch (JsonException) {
$v = [];
}catch (PDOException $e) {
throw new StorageException('Save failed', 0, $e);
}Throwing
corethrow new InvalidArgumentException('Age must be positive');$user = find($id) ?? throw new NotFoundException("User $id");$fn = fn($v) => $v > 0 ? $v : throw new RangeException();class PaymentFailed extends RuntimeException
{
public function __construct(
public readonly string $reference,
string $message = 'Payment failed',
) {
parent::__construct($message);
}
}$e->getMessage();
$e->getCode();
$e->getFile();
$e->getLine();
$e->getPrevious();
$e->getTraceAsString();Exception Hierarchy
coreThrowable
├── Error engine level failures
│ ├── TypeError
│ ├── ValueError
│ ├── ArgumentCountError
│ ├── ArithmeticError
│ │ └── DivisionByZeroError
│ └── AssertionError
└── Exception application level failures
├── ErrorException
├── JsonException
├── RuntimeException
└── LogicExceptionBadFunctionCallException
BadMethodCallException
DomainException
InvalidArgumentException
LengthException
OutOfRangeExceptionOutOfBoundsException
OverflowException
RangeException
UnderflowException
UnexpectedValueExceptiontry {
$r = intdiv(1, 0);
} catch (DivisionByZeroError $e) { }Error Reporting
coredeclare(strict_types=1);
error_reporting(E_ALL);
ini_set('display_errors', '1');error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php/app.log');error_log('Cache miss for key ' . $key);E_ALL every message
E_ERROR fatal runtime error
E_WARNING non fatal warning
E_NOTICE likely a bug
E_DEPRECATED scheduled for removal
E_USER_ERROR raised by trigger_errortrigger_error('Legacy call used', E_USER_DEPRECATED);Handlers
coreset_error_handler(function (int $no, string $msg, string $file, int $line) {
throw new ErrorException($msg, 0, $no, $file, $line);
});set_exception_handler(function (Throwable $e) {
error_log($e->getMessage());
http_response_code(500);
echo 'Something went wrong';
});register_shutdown_function(function () {
$err = error_get_last();
if ($err && $err['type'] === E_ERROR) {
error_log($err['message']);
}
});restore_error_handler();
restore_exception_handler();Debugging
corevar_dump($value);
die();echo '<pre>';
print_r($data, false);
echo '</pre>';debug_print_backtrace();
$trace = debug_backtrace();$start = hrtime(true);
// work
$ms = (hrtime(true) - $start) / 1_000_000;
memory_get_usage(true);
memory_get_peak_usage(true);get_class_methods($obj);
get_object_vars($obj);
(new ReflectionClass($obj))->getProperties();Dates & Maths
Formatting Dates
coreecho date('Y-m-d H:i:s');echo date('d/m/Y', 1735689600);Y 2026 y 26 m 07 n 7
d 29 j 29 H 14 G 14
i 05 s 11 D Wed l Wednesday
M Jul F July N 3 w 3
A PM a pm g 2 U timestampdate(DATE_ATOM); // 2026-07-29T14:05:11+00:00
date('c'); // same as ATOM
date('r'); // RFC 2822$now = time();
$ms = (int) (microtime(true) * 1000);$ts = mktime(9, 0, 0, 12, 25, 2026);strtotime('2026-07-29');
strtotime('+1 week');
strtotime('next monday');
strtotime('last day of this month');DateTime Objects
core$d = new DateTimeImmutable('2026-07-29 10:00');
echo $d->format('D, d M Y');$later = $d->modify('+3 days');
$prev = $d->sub(new DateInterval('P1M'));
$next = $d->add(new DateInterval('PT2H30M'));P1Y2M3D 1 year 2 months 3 days
PT4H5M6S 4 hours 5 minutes 6 seconds
P1W 1 week$diff = $start->diff($end);
echo $diff->days; // total days
echo $diff->y, $diff->m, $diff->d;
echo $diff->format('%a days');$d = DateTimeImmutable::createFromFormat('d/m/Y', '29/07/2026');if ($start < $end) { }
if ($due <= new DateTimeImmutable()) { }$ts = $d->getTimestamp();
$d = (new DateTimeImmutable())->setTimestamp($ts);Time Zones
coredate_default_timezone_set('Europe/Bucharest');$d = new DateTimeImmutable('now', new DateTimeZone('UTC'));$local = $utc->setTimezone(new DateTimeZone('America/New_York'));$stored = new DateTimeImmutable($row['created_at'], new DateTimeZone('UTC'));
$display = $stored->setTimezone(new DateTimeZone($user->tz));DateTimeZone::listIdentifiers();Date Helpers
corecheckdate(2, 30, 2026); // false$age = (new DateTimeImmutable($dob))
->diff(new DateTimeImmutable())->y;$first = new DateTimeImmutable('first day of this month 00:00');
$last = new DateTimeImmutable('last day of this month 23:59:59');$period = new DatePeriod(
new DateTimeImmutable('2026-01-01'),
new DateInterval('P1D'),
new DateTimeImmutable('2026-01-08')
);
foreach ($period as $day) {
echo $day->format('Y-m-d');
}$mins = (time() - $ts) / 60;
$label = match (true) {
$mins < 1 => 'just now',
$mins < 60 => round($mins) . 'm ago',
$mins < 1440 => round($mins / 60) . 'h ago',
default => date('d M', $ts),
};$isWeekend = in_array((int) date('N', $ts), [6, 7], true);Numbers & Maths
coreround(3.456, 2); // 3.46
floor(3.9); // 3
ceil(3.1); // 4
round(2.5); // 3, half away from zeroabs(-7); // 7
$sign = $n <=> 0;intdiv(17, 5); // 3
17 % 5; // 2
fmod(17.5, 5); // 2.52 ** 8; // 256
pow(2, 8); // 256
sqrt(144); // 12
hypot(3, 4); // 5log(M_E); log10(1000); exp(1);
M_PI; M_E; PHP_FLOAT_EPSILON;if (abs($a - $b) < PHP_FLOAT_EPSILON) { }$safe = max($min, min($max, $value));bcadd('0.1', '0.2', 2); // 0.30
bcmul($price, $qty, 2);
bccomp($a, $b, 2);Random Values
core$n = random_int(1, 100);$token = bin2hex(random_bytes(32));mt_rand(1, 6);
mt_rand();
random_int(0, PHP_INT_MAX); // prefer this$pick = $items[array_rand($items)];shuffle($deck);$b = random_bytes(16);
$b[6] = chr((ord($b[6]) & 0x0f) | 0x40);
$b[8] = chr((ord($b[8]) & 0x3f) | 0x80);
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));Number Formatting
corenumber_format(1234.5, 2); // 1,234.50
number_format(1234.5, 2, ',', ' '); // 1 234,50$f = new NumberFormatter('en_GB', NumberFormatter::CURRENCY);
echo $f->formatCurrency(1234.5, 'GBP');echo round($part / $total * 100, 1) . '%';$units = ['B', 'KB', 'MB', 'GB'];
$i = $bytes > 0 ? (int) floor(log($bytes, 1024)) : 0;
echo round($bytes / 1024 ** $i, 2) . ' ' . $units[$i];base_convert('ff', 16, 2); // 11111111
bindec('1010'); // 10
dechex(255); // ff
decbin(10); // 1010is_numeric('12.5'); // true
is_numeric('12abc'); // false
ctype_digit('123'); // true, string of digits onlySecurity & Regex
Passwords
core$hash = password_hash($plain, PASSWORD_DEFAULT);if (password_verify($plain, $user->hash)) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user->id;
}if (password_needs_rehash($user->hash, PASSWORD_DEFAULT)) {
$new = password_hash($plain, PASSWORD_DEFAULT);
saveHash($user->id, $new);
}password_hash($plain, PASSWORD_BCRYPT, ['cost' => 12]);
password_hash($plain, PASSWORD_ARGON2ID);password_get_info($hash);Output & Input Safety
coreecho htmlspecialchars($comment, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');function e(?string $v): string {
return htmlspecialchars($v ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// <p><?= e($name) ?></p>htmlspecialchars($v) // HTML body and attributes
rawurlencode($v) // URL path segment
json_encode($v) // inside a script block$plain = strip_tags($html);
$some = strip_tags($html, '<p><a><strong>');$email = trim($_POST['email'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Enter a valid email address';
}// safe
$stmt = $pdo->prepare('SELECT * FROM u WHERE email = ?');
$stmt->execute([$email]);
// never do this
$pdo->query("SELECT * FROM u WHERE email = '$email'");Tokens & Hashing
coreif (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}<input type="hidden" name="csrf"
value="<?= e($_SESSION['csrf']) ?>">if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
http_response_code(419);
exit('Session expired, reload the page');
}hash('sha256', $payload);
hash_file('sha256', $path);$sig = hash_hmac('sha256', $payload, $secret);$enc = rtrim(strtr(base64_encode($raw), '+/', '-_'), '=');md5($file); // checksum only
sha1($file); // checksum onlyRegex Basics
coreif (preg_match('/^(\d{4})-(\d{2})$/', $s, $m)) {
[$all, $year, $month] = $m;
}preg_match('/(?<year>\d{4})-(?<month>\d{2})/', $s, $m);
echo $m['year'];preg_match_all('/#(\w+)/', $text, $m);
$tags = $m[1];preg_replace('/\s+/', ' ', $messy);preg_replace('/(\w+)@(\w+)/', '$2 at $1', $s);preg_replace_callback('/\d+/',
fn($m) => $m[0] * 2,
'a1 b2'
); // a2 b4preg_split('/[\s,]+/', 'a, b c');$safe = preg_quote($input, '/');Regex Reference
core. any character except newline
\d digit \D non digit
\w word char \W non word
\s whitespace \S non whitespace
[abc] one of a b c
[^a] anything but a
[a-z] range* zero or more
+ one or more
? zero or one
{3} exactly three
{2,} two or more
{2,5} between two and five
*? lazy, matches as little as possible^ start of subject or line
$ end of subject or line
\b word boundary
\A start of subject only
\z end of subject only(abc) capturing group
(?:abc) non capturing
(?<n>abc) named group
a|b either a or b/pattern/i case insensitive
/pattern/m multiline, ^ and $ per line
/pattern/s dot matches newline too
/pattern/u UTF-8 mode, use it for user text
/pattern/x ignore whitespace in the pattern'/^[\w.+-]+@[\w-]+\.[\w.]+$/' email shape
'/^\+?[0-9\s-]{7,15}$/' phone shape
'/^[a-z0-9]+(?:-[a-z0-9]+)*$/' slug
'/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i' hex colourEnvironment & Config
core$dsn = getenv('DATABASE_URL') ?: throw new RuntimeException('DATABASE_URL missing');ini_set('memory_limit', '512M');
ini_get('upload_max_filesize');echo PHP_VERSION;
if (PHP_VERSION_ID < 80200) {
exit('PHP 8.2 or newer required');
}if (!extension_loaded('pdo_mysql')) {
exit('pdo_mysql is required');
}set_time_limit(0);
ignore_user_abort(true);if (PHP_SAPI === 'cli') {
$args = $argv;
}display_errors = Off
expose_php = Off
session.cookie_httponly = 1
session.cookie_secure = 1
allow_url_include = OffSerialization Safety
core// dangerous, attacker controls which classes get built
$data = unserialize($_COOKIE['prefs']);$data = unserialize($raw, ['allowed_classes' => false]);
$data = unserialize($raw, ['allowed_classes' => [Prefs::class]]);$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);if (!json_validate($raw)) {
throw new UnexpectedValueException('Bad JSON');
}$payload = base64_encode(json_encode($data));
$sig = hash_hmac('sha256', $payload, $secret);
// on return
if (!hash_equals($sig, hash_hmac('sha256', $payload, $secret))) {
exit('Tampered payload');
}Request Forgery & Redirects
core// SSRF: the attacker can reach your internal network
$body = file_get_contents($_GET['url']);$host = parse_url($url, PHP_URL_HOST);
if (!in_array($host, ['api.partner.com'], true)) {
exit('Host not allowed');
}$ip = gethostbyname($host);
if (!filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
exit('Blocked address');
}curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);$next = $_GET['next'] ?? '/';
if (!str_starts_with($next, '/') || str_starts_with($next, '//')) {
$next = '/';
}
header('Location: ' . $next, true, 303);
exit;$name = str_replace(["\r", "\n"], '', $userValue);
header('X-Requested-Name: ' . $name);filter_var Reference
coreFILTER_VALIDATE_EMAIL
FILTER_VALIDATE_URL
FILTER_VALIDATE_INT
FILTER_VALIDATE_FLOAT
FILTER_VALIDATE_IP
FILTER_VALIDATE_MAC
FILTER_VALIDATE_REGEXP
FILTER_VALIDATE_DOMAIN
FILTER_VALIDATE_BOOLEANFILTER_SANITIZE_EMAIL
FILTER_SANITIZE_URL
FILTER_SANITIZE_NUMBER_INT
FILTER_SANITIZE_NUMBER_FLOAT
FILTER_SANITIZE_SPECIAL_CHARS
FILTER_SANITIZE_FULL_SPECIAL_CHARS
FILTER_UNSAFE_RAWFILTER_FLAG_ALLOW_FRACTION
FILTER_FLAG_ALLOW_THOUSAND
FILTER_FLAG_IPV4 / FILTER_FLAG_IPV6
FILTER_FLAG_NO_PRIV_RANGE
FILTER_FLAG_NO_RES_RANGE
FILTER_FLAG_PATH_REQUIRED
FILTER_NULL_ON_FAILURE$age = filter_var($raw, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 0, 'max_range' => 130],
]);filter_var('yes', FILTER_VALIDATE_BOOLEAN); // true
filter_var('off', FILTER_VALIDATE_BOOLEAN); // false
filter_var('maybe', FILTER_VALIDATE_BOOLEAN,
FILTER_NULL_ON_FAILURE); // null$clean = filter_var_array($_POST, [
'email' => FILTER_VALIDATE_EMAIL,
'age' => FILTER_VALIDATE_INT,
]);Tooling & Testing
Composer
corecomposer init
composer require monolog/monolog
composer require --dev phpunit/phpunitcomposer install # from composer.lock, use in CI and production
composer update # resolve fresh, updates the lock file
composer update vendor/pkg
composer install --no-dev --optimize-autoloader^1.2.3 >=1.2.3 <2.0.0 any compatible release
~1.2.3 >=1.2.3 <1.3.0 patch level only
1.2.* >=1.2.0 <1.3.0
>=1.2 no upper bound, avoid this
1.2.3 pinned exactlycomposer show
composer show --tree
composer why vendor/package
composer outdated
composer audit # known security advisories{
"autoload": {
"psr-4": { "App\\": "src/" },
"files": ["src/helpers.php"]
},
"autoload-dev": {
"psr-4": { "Tests\\": "tests/" }
}
}composer dump-autoload
composer dump-autoload --optimize # for production{
"scripts": {
"test": "phpunit",
"lint": "php-cs-fixer fix --dry-run --diff",
"check": ["@lint", "@test"]
}
}require __DIR__ . '/vendor/autoload.php';PHPUnit
coreuse PHPUnit\Framework\TestCase;
final class MoneyTest extends TestCase
{
public function test_it_adds_amounts(): void
{
$sum = (new Money(100))->plus(new Money(50));
$this->assertSame(150, $sum->amount);
}
}$this->assertSame($expected, $actual); // strict, prefer this
$this->assertEquals($expected, $actual); // loose
$this->assertTrue($v); $this->assertFalse($v);
$this->assertNull($v); $this->assertNotNull($v);
$this->assertCount(3, $array);
$this->assertContains('a', $array);
$this->assertInstanceOf(User::class, $obj);
$this->assertStringContainsString('php', $text);$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Age must be positive');
new Person(age: -1);#[DataProvider('slugCases')]
public function test_it_slugifies(string $in, string $out): void
{
$this->assertSame($out, slugify($in));
}
public static function slugCases(): array
{
return [
'spaces' => ['Hello World', 'hello-world'],
'accents' => ['Café Noir', 'cafe-noir'],
];
}protected function setUp(): void
{
parent::setUp();
$this->pdo = new PDO('sqlite::memory:');
}
protected function tearDown(): void
{
unset($this->pdo);
parent::tearDown();
}$mailer = $this->createMock(Mailer::class);
$mailer->expects($this->once())
->method('send')
->with($this->equalTo('ada@example.com'))
->willReturn(true);vendor/bin/phpunit
vendor/bin/phpunit --filter test_it_adds_amounts
vendor/bin/phpunit --testsuite unit
vendor/bin/phpunit --coverage-html build/coverage<?xml version="1.0"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="unit">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>Command Line
corephp -v
php -m
php -i | grep memory_limitphp -S localhost:8000 -t publicphp -l index.phpphp -r 'echo PHP_VERSION;'
php -a// php import.php users.csv --dry-run
$script = $argv[0];
$file = $argv[1] ?? null;
$dryRun = in_array('--dry-run', $argv, true);
echo $argc . ' arguments';$opts = getopt('f:v', ['file:', 'verbose', 'dry-run']);
$file = $opts['file'] ?? $opts['f'] ?? null;while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}fwrite(STDERR, "Import failed\n");
exit(1); // non zero tells the shell it failedif (PHP_SAPI !== 'cli') {
exit('Command line only');
}Static Analysis & Style
corecomposer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level 8parameters:
level: 8
paths:
- src
- tests
ignoreErrors:
- '#Call to an undefined method#'composer require --dev vimeo/psalm
vendor/bin/psalm --init
vendor/bin/psalmcomposer require --dev friendsofphp/php-cs-fixer
vendor/bin/php-cs-fixer fix --dry-run --diff
vendor/bin/php-cs-fixer fixvendor/bin/phpcs --standard=PSR12 src
vendor/bin/phpcbf --standard=PSR12 srccomposer require --dev rector/rector
vendor/bin/rector process src --dry-run/** @param list<array{id:int, name:string}> $rows */
function render(array $rows): string { }
/** @var non-empty-string $slug */PSR Standards
corePSR-1 basic coding standard
PSR-3 logger interface
PSR-4 autoloading from namespace to path
PSR-6 caching interface
PSR-7 HTTP message interfaces
PSR-11 container interface
PSR-12 extended coding style
PSR-14 event dispatcher
PSR-15 HTTP server request handlers
PSR-18 HTTP client// App\Service\Mailer -> src/Service/Mailer.php
{ "autoload": { "psr-4": { "App\\": "src/" } } }use Psr\Log\LoggerInterface;
public function __construct(
private LoggerInterface $logger,
) {}
$this->logger->error('Payment failed', ['order' => $id]);emergency alert critical error
warning notice info debugPerformance
coreopcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 # requires a deploy reload
opcache.jit_buffer_size=100Mvar_dump(opcache_get_status()['opcache_enabled']);
opcache_reset(); // clear after a deploy// opcache.preload=/app/preload.php
$files = glob(__DIR__ . '/src/**/*.php');
foreach ($files as $file) {
opcache_compile_file($file);
}// slow: one query per row
foreach ($posts as $post) {
$post->author = findAuthor($post->author_id);
}
// fast: one query for all of them
$ids = array_column($posts, 'author_id');
$authors = findAuthorsById($ids);$t = hrtime(true);
$work();
printf("%.2f ms\n", (hrtime(true) - $t) / 1e6);
printf("%.1f MB\n", memory_get_peak_usage(true) / 1048576);XDEBUG_MODE=profile php script.php
XDEBUG_MODE=debug php -S localhost:8000
# read the cachegrind file in KCachegrind or PhpStorm0 results
Nothing matches that. Try one word instead of several, or reset the version filter to All.
What PHP Is and Why It Still Runs the Web
PHP is a server side scripting language built for the web. Your browser never sees it. The server runs the code, produces HTML or JSON, and sends only the result down the wire.
Rasmus Lerdorf released the first version in 1995. Three decades later PHP powers WordPress, Laravel, Symfony, Drupal, Magento and a very large share of every site you visit.
The modern language is not the PHP of 2005. Typed properties, enums, readonly classes, match expressions, attributes and a genuinely fast engine landed between PHP 7.0 and PHP 8.4. If your mental model dates from the mysql_* era, the snippets above are worth a slow read.
This PHP cheat sheet covers the whole surface: syntax, strings, arrays, control flow, functions, objects, requests, files, databases, errors, dates, security and tooling. Search it, filter it by version, copy what you need.
PHP Syntax Basics
Tags and Statements
Every block of PHP opens with <?php. In files that contain only PHP, leave the closing tag off. A stray newline after ?> gets sent to the browser and breaks header calls later.
<?php
declare(strict_types=1);
echo "Hello, world";
Statements end with a semicolon. Blocks use braces. Whitespace is free, so format for the reader.
Echoing Into Templates
Inside a template, the short echo tag keeps the markup readable:
<ul>
<?php foreach ($items as $item): ?>
<li><?= htmlspecialchars($item->name) ?></li>
<?php endforeach; ?>
</ul>
The alternative syntax with endforeach, endif and endwhile exists precisely for this. Braces buried in HTML are hard to match by eye.
Comments
Use // for a line, /* */ for a block, and a docblock when a function needs explaining. Modern editors read docblocks for autocomplete, so the effort pays back.
Variables, Types and Type Juggling
Variables start with $ and need no declaration. PHP infers the type from the value, which is convenient and occasionally dangerous.
Strict Types
Add declare(strict_types=1); as the first statement in every file. Without it, passing the string "5" to a function expecting int silently converts. With it, you get a TypeError at the boundary where the bug actually is.
Comparison Traps
Use === unless you have a specific reason not to. Loose comparison applies conversion rules that surprise people:
0 == "foo"; // false in PHP 8, true before it
"1" == "01"; // true, both read as numbers
null == false; // true
[] == false; // true
"abc" === "abc" // true, and unambiguous
PHP 8 rewrote string to number comparison so that a non numeric string is compared as a string rather than being cast to zero. That single change fixed a whole family of authentication bugs.
Falsy Values
These evaluate as false in a boolean context: false, 0, 0.0, "", "0", [] and null. Note that the string "0" is falsy while "0.0" is truthy, which is exactly the kind of thing === saves you from.
Working With Strings
Quotes Matter
Single quotes are literal. Double quotes parse variables and escape sequences. For anything with no interpolation, single quotes are marginally faster and always clearer about intent.
$name = 'Ada';
echo 'Hi $name'; // Hi $name
echo "Hi $name"; // Hi Ada
echo "Hi {$user['name']}"; // braces for complex expressions
Heredoc for Blocks
Heredoc keeps multi line templates readable without a pile of concatenation. Nowdoc, with the marker in single quotes, parses nothing at all and is ideal for raw SQL or code samples.
Multibyte Safety
If your text can contain accents, emoji or any non ASCII character, use the mb_ family. strlen('café') returns 5 because it counts bytes. mb_strlen('café') returns 4 because it counts characters. Set mb_internal_encoding('UTF-8') once at bootstrap.
Arrays: The PHP Workhorse
A PHP array is an ordered map. The same structure serves as a list, a dictionary, a stack and a queue, which is why almost every PHP function seems to take or return one.
Functional Style
Three functions cover most transformations:
- array_map changes every element and keeps the count the same.
- array_filter keeps the elements that pass a test and drops the rest.
- array_reduce collapses the whole array into a single value.
$total = array_reduce(
array_filter($orders, fn($o) => $o['paid']),
fn($sum, $o) => $sum + $o['amount'],
0
);
One thing to watch: array_filter preserves keys. If you plan to json_encode the result and expect a JSON array rather than an object, wrap it in array_values.
Sorting
The sort family is easy to mix up, so here it is in one place:
| Function | Sorts by | Keeps keys |
|---|---|---|
| sort / rsort | Value | No |
| asort / arsort | Value | Yes |
| ksort / krsort | Key | Yes |
| usort | Callback | No |
| uasort | Callback on value | Yes |
| uksort | Callback on key | Yes |
The spaceship operator makes comparison callbacks trivial: fn($a, $b) => $a['age'] <=> $b['age']. Compare arrays of fields to sort by several columns at once.
Control Flow and the Match Expression
match arrived in PHP 8 and replaces most switch blocks. It compares strictly, returns a value, needs no break, and throws if nothing matches instead of falling through silently.
$label = match (true) {
$bytes < 1024 => $bytes . ' B',
$bytes < 1048576 => round($bytes / 1024) . ' KB',
default => round($bytes / 1048576, 1) . ' MB',
};
Reach for switch only when you deliberately want fall through behaviour or loose comparison.
Null Handling
Three operators cover almost every null case. ?? reads a value with a fallback and never warns about a missing key. ??= assigns only when the target is null. ?-> stops a method chain at the first null rather than throwing.
$page = $_GET['page'] ?? 1;
$opts['retries'] ??= 3;
$city = $order?->customer?->address?->city;
Functions and Closures
Type Everything
Parameter types, return types and property types are checked by the engine and understood by static analysis tools. They are the cheapest bug prevention available.
function discount(float $price, float $percent = 10.0): float
{
return round($price * (1 - $percent / 100), 2);
}
Named Arguments
Since PHP 8 you can pass arguments by name, which removes the need to remember positions and makes long signatures readable at the call site.
createUser(name: 'Ada', admin: true, notify: false);
Arrow Functions
Arrow functions capture the outer scope automatically and hold a single expression. They are made for callbacks. Use a full closure with use when you need several statements or want to be explicit about what is captured.
Generators
A function containing yield returns a generator instead of an array. Values are produced one at a time, so you can iterate a two gigabyte log file inside a few kilobytes of memory. This is the single biggest memory win available in PHP, and it costs one keyword.
Object Oriented PHP
Constructor Promotion
PHP 8 collapsed the old four line pattern of declaring a property, documenting it, accepting it and assigning it into one declaration:
final class Money
{
public function __construct(
public readonly int $amount,
public readonly string $currency = 'EUR',
) {}
}
readonly means the property can be written once, from inside the class. Combined with promotion it gives you a real value object in six lines.
Enums
Before PHP 8.1 people used class constants and hoped nobody passed a bad string. Enums make the invalid state unrepresentable:
enum OrderStatus: string
{
case Pending = 'pending';
case Shipped = 'shipped';
case Refunded = 'refunded';
public function isFinal(): bool
{
return $this === self::Refunded;
}
}
Type hint OrderStatus and the engine guarantees the value is one of three. Use tryFrom() when parsing untrusted input and from() when a bad value should throw.
Interfaces Versus Traits
An interface is a promise about behaviour with no implementation. A trait is shared implementation with no promise. Use interfaces for the contract your callers depend on, and traits sparingly to share mechanical code such as timestamps or event dispatch.
Property Hooks
PHP 8.4 added hooks, which let a plain property run code on read or write. Boilerplate getters and setters mostly disappear, and you can add validation to an existing public property without changing a single caller.
Handling Requests, Forms and Sessions
Never Trust Input
Everything in $_GET, $_POST, $_COOKIE and the request headers is attacker controlled. Validate the shape, cast to the type you expect, and escape at the point of output.
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
http_response_code(400);
exit('Invalid id');
}
The Post Redirect Get Pattern
After a successful form submission, redirect instead of rendering. It stops the browser from resubmitting on refresh:
if ($_SERVER['REQUEST_METHOD'] === 'POST' && save($data)) {
header('Location: /orders/' . $id, true, 303);
exit;
}
Always call exit after a redirect. Without it the script keeps running and can still emit output or write to the database.
Sessions Done Properly
Configure the session cookie as HTTP only, secure and SameSite. Call session_regenerate_id(true) immediately after a successful login to prevent session fixation. On logout, clear the array and destroy the session rather than just unsetting one key.
Databases With PDO
PDO is the portable database layer. Configure it once, correctly, and most classes of database bug simply stop happening:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
Exception mode means a failed query throws rather than returning false that you forget to check. Turning off emulated prepares sends real prepared statements to the server, so types are respected and injection is structurally impossible.
Prepared Statements Are Not Optional
String interpolation into SQL is the root of SQL injection. A placeholder is never parsed as SQL, so no input can change the meaning of the query.
// vulnerable
$pdo->query("SELECT * FROM users WHERE email = '$email'");
// safe
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
Transactions
Any operation that writes to more than one table belongs in a transaction. Wrap it in try and catch, commit on success, roll back on any throwable, and rethrow so the caller knows.
Errors and Exceptions
PHP has two throwable branches. Exception covers application problems you expect to handle. Error covers engine failures such as a type mismatch or a call to a missing method. Both implement Throwable, so catching Throwable catches everything.
try {
$report = $generator->build($input);
} catch (InvalidArgumentException $e) {
$errors[] = $e->getMessage();
} catch (Throwable $e) {
error_log($e->getMessage());
http_response_code(500);
}
In production, log errors and show nothing. A stack trace on screen tells an attacker your directory structure, framework version and database driver. Set display_errors to Off and log_errors to On.
Security Checklist
- Passwords:
password_hashandpassword_verify, nothing else. Never md5, never sha1, never your own salt scheme. - SQL: prepared statements with bound parameters on every query that touches user input.
- Output:
htmlspecialcharswithENT_QUOTESon every value echoed into HTML. - CSRF: a random per session token in every state changing form, compared with
hash_equals. - Sessions: HTTP only, secure, SameSite cookies, and regenerate the id on login.
- Uploads: check the size, verify the type from the file contents, rename the file, and store it outside the web root.
- Randomness:
random_bytesandrandom_intfor anything security related.randandmt_randare predictable. - Config: keep credentials in environment variables, never in a file inside the document root.
PHP Version Reference
Knowing which release introduced a feature saves you from writing code your server cannot run. Every snippet in the PHP cheat sheet above is tagged with the version that introduced it, so the version filter narrows the whole reference down to what your server will actually run. This is what each release brought:
| Version | Headline features |
|---|---|
| 7.0 | Scalar and return types, spaceship operator, null coalescing, big speed gains |
| 7.1 | Nullable types, void return, class constant visibility, multi catch |
| 7.4 | Typed properties, arrow functions, spread in arrays, null coalescing assignment |
| 8.0 | Named arguments, attributes, constructor promotion, match, nullsafe operator, union types, JIT |
| 8.1 | Enums, readonly properties, fibers, first class callables, never return type |
| 8.2 | Readonly classes, disjunctive normal form types, standalone null and false types |
| 8.3 | Typed class constants, dynamic class constant fetch, json_validate |
| 8.4 | Property hooks, asymmetric visibility, new without parentheses, array find and any and all |
Check what you are running with php -v on the command line or PHP_VERSION in code. Anything below 8.1 no longer receives security patches, so upgrading is the highest value maintenance work available.
Performance Notes That Actually Matter
Micro optimising loops is rarely where the time goes. These four changes are:
- OPcache: on by default in production builds. Verify it is enabled. It compiles your code once instead of on every request.
- The N plus 1 query: loading a list then querying inside the loop. Fetch the related rows in one query keyed by id instead.
- Generators over arrays: any time you build a large array only to loop over it once, yield instead.
- Persistent connections and indexes: most slow PHP pages are actually slow SQL. Profile the query before rewriting the PHP.
Bookmark this PHP cheat sheet and use the search box rather than scrolling. Typing two words narrows nearly six hundred snippets down to the handful you actually want, and the copy button on every one of them means you never retype a signature from memory.
Frequently Asked Questions
Is PHP still worth learning in 2026?
Yes, on the evidence. PHP runs a large share of the web, WordPress alone accounts for a significant portion of all sites, and Laravel and Symfony remain in heavy commercial use. The job market for maintaining and extending PHP systems is substantial and unlikely to shrink quickly.
The language people dismiss is usually PHP 5. Modern PHP is typed, fast and pleasant to work in.
What is the difference between echo and print?
echo accepts multiple comma separated arguments and returns nothing. print takes one argument and returns 1, which means it can be used inside an expression. In practice echo is the one to use.
When should I use == instead of ===?
Almost never. Use === by default so that both the value and the type must match. Loose comparison converts operands before comparing, and the rules are subtle enough that they have caused real security bugs. If you need a numeric comparison of a string, cast explicitly instead.
PDO or MySQLi?
PDO, in most cases. It supports a dozen database drivers with the same API, has named placeholders, and its exception mode plus fetch mode configuration is cleaner. MySQLi is MySQL only and is worth using when you specifically need a MySQL feature PDO does not expose.
How do I stop SQL injection?
Use prepared statements with bound parameters for every query containing user input, and never build SQL by concatenating strings. Placeholders are sent to the database separately from the query, so input cannot change what the query does. Escaping functions are not a substitute.
What does declare(strict_types=1) actually do?
It changes type checking from coercive to strict for the file it appears in. Without it, passing "5" where an int is expected silently converts the value. With it, that call throws a TypeError. It only affects the file containing the declaration, so add it to every file.
Should I use readonly or a getter?
Use readonly for value objects that should never change after construction. It is enforced by the engine and needs no extra code. Reach for a getter or a property hook when the value is computed, needs validation on write, or the storage format differs from what callers should see.
Why does my header call say headers already sent?
Something was output before the call. Common causes are a blank line before <?php, whitespace after a closing ?> in an included file, or a stray echo. Omit closing tags in pure PHP files and check every include in the chain.
Official Documentation for Every Category on This Sheet
Every snippet above is checked against the PHP manual rather than written from memory. These are the primary sources behind each category, worth bookmarking alongside the sheet itself:
- Basics: PHP basic syntax
- Strings: string functions reference
- Arrays: array functions reference
- Control Flow: control structures and the match expression
- Functions: function reference and arrow functions
- Classes & OOP: the OOP chapter
- Web & Requests: superglobals and sessions
- Files & JSON: filesystem functions and JSON functions
- Database: PDO
- Errors: exceptions
- Dates & Maths: DateTime and math functions
- Security & Regex: password hashing and PCRE
- Tooling & Testing: Composer and PHPUnit
For what shipped in each release, see the official PHP release notes.
How This Cheat Sheet Is Kept Current
Last updated 29 July 2026. The sheet is re-checked after every PHP release and whenever a snippet is reported as drifting from the manual.
What that check involves:
- Function signatures and behaviour are read from the PHP manual for the version in question, not from memory or old notes.
- Version tags (7.0+, 8.0+, and so on) are set from the release that introduced the feature, cross-checked against the official release notes.
- Security guidance follows current PHP core recommendations, not habits carried over from PHP 5.
- Anything deprecated or removed in a supported release is flagged rather than left to look current.
Written and maintained by Bogdan Sandu at TMS Outsource. If a snippet looks wrong or out of date, the fastest fix is to check it against the linked manual page above and let us know - corrections get applied to the sheet, and the date at the top of this section moves with them.