PHP Language Fundamentals
PHP has evolved from a simple templating language into a robust, general‑purpose backend tool. Modern PHP development relies on a strong grasp of the language itself—types, control flow, functions, and arrays—before you ever touch a framework. This article covers those core fundamentals from an engineering perspective, emphasizing predictable behavior, type safety, and patterns that scale to real applications.
After reading, you’ll be able to write clear, modern PHP that takes full advantage of the language’s type system and avoids common pitfalls that still trip up developers stuck in older habits.
What You Will Learn​
- How PHP scripts are structured and executed
- Working with variables, constants, and types
- Writing type‑safe code with strict comparisons and type declarations
- Using strings, numbers, and operators effectively
- Mastering PHP’s versatile arrays and avoiding typical mistakes
- Controlling program flow with modern
match,if, and loops - Designing functions that are predictable and self‑documenting
- Handling null and error‑prone values safely
- Using exceptions responsibly
- Applying modern PHP 8.x features like enums, readonly properties, and constructor promotion
- Building a complete, well‑typed example that ties these concepts together
PHP Syntax and Program Structure​
Every PHP script starts with the opening <?php tag. In files that contain only PHP code—virtually all backend scripts—the closing ?> tag must be omitted to prevent accidental trailing whitespace from being sent as output and interfering with HTTP headers.
<?php
declare(strict_types=1);
echo "Hello, PHPDevPro!";
Statements end with a semicolon. Whitespace is generally ignored, but consistent indentation improves readability. PHP supports single‑line comments with // or #, and multi‑line comments with /* … */. Keywords and function names are case‑insensitive, but variable names are case‑sensitive. A good habit is to treat everything except variables as case‑sensitive for consistency.
The fundamental unit of execution is the statement, grouped together in a file that the PHP engine parses and executes from top to bottom. For larger programs, you organize code into functions and classes, which are covered in Object‑Oriented Programming.
Variables and Constants​
Variables are prefixed with $ and do not require a declared type. You assign a value and PHP infers the type. However, relying on implicit type changes can produce surprising behavior; modern PHP encourages declaring types wherever possible.
$count = 42; // int
$name = 'PHPDevPro'; // string
$price = 9.99; // float
Constants hold values that never change during script execution. Define them with const at the top level of a file, or inside a class. The older define() still works, but const is generally preferred because it supports namespaces and can be used in array/object expressions.
const VERSION = '8.4';
const HOST = 'localhost';
echo VERSION; // 8.4
PHP has different variable scopes: global, local (inside functions), and class properties. By default, variables inside functions are local; you must explicitly reference the global scope with the global keyword or the $GLOBALS array, but using global variables is discouraged because it makes code hard to reason about. We’ll explore better alternatives when we discuss Dependency Injection.
Null represents a variable with no value. An uninitialized variable results in a warning; modern code should initialize variables before use. In double‑quoted strings, variables are interpolated directly.
$label = "Version: " . VERSION; // concatenation
$label = "Version: $label"; // interpolation
For predictable code, always initialize variables, rely on constants for fixed values, and avoid global.
PHP Types​
PHP supports several built‑in types:
| Type | Description |
|---|---|
null | A variable with no value |
bool | true or false |
int | Whole numbers (64‑bit on most systems) |
float | Floating‑point numbers |
string | Sequence of characters |
array | Ordered map (can be list or associative) |
object | Instance of a class |
callable | Anything that can be called (function, method, closure) |
iterable | Can be iterated with foreach |
resource | External resource (file handle, DB connection) – rarely used directly today |
Modern PHP allows you to add type declarations to function parameters, return values, and class properties. Combined with declare(strict_types=1);, these declarations cause a TypeError when a value of the wrong type is passed, instead of silently coercing it.
declare(strict_types=1);
function multiply(int $a, int $b): int {
return $a * $b;
}
echo multiply(4, 5); // 20
// multiply(4.5, 5); // TypeError in strict mode
Without strict_types=1, PHP would attempt to coerce 4.5 into 4 with a deprecation notice. That loose typing behavior is a legacy of PHP’s early days; strict mode makes the type system more reliable.
Important: declare(strict_types=1); applies only to the file that declares it, not to the whole application. It also does not affect code in other files that are included or called. It is not a global switch; each file must declare it independently if you want strict typing across a codebase.
Comparisons and truthiness: The difference between == (loose) and === (strict) is critical. Loose comparison attempts type coercion and can hide bugs:
var_dump(0 == ''); // true (both coerced to falsy values)
var_dump(0 === ''); // false (different types)
Always prefer === and !== unless you specifically need loose comparison. This avoids entire classes of subtle bugs.
To explicitly cast a value, use (int), (string), (bool), etc. For example, (int) "123" yields the integer 123. Casting should be used deliberately and documented.
Strings​
PHP offers several ways to define strings:
- Single‑quoted:
'hello'– no interpolation, no escape sequences except\\and\'. - Double‑quoted:
"hello $name"– interpolates variables and interprets escape sequences like\n. - Heredoc: starts with
<<<EOTand ends withEOT;– behaves like a double‑quoted string. - Nowdoc:
<<<'EOT'– like single‑quoted, no interpolation.
$name = 'World';
echo "Hello, $name!"; // Hello, World!
echo 'Hello, $name!'; // Hello, $name!
$heredoc = <<<EOT
Line 1
Line 2
EOT;
The . operator concatenates strings. Since PHP 8.2 you can also use string interpolation with more complex expressions using {$expr}.
Common string functions include strlen() (byte length), trim(), str_contains(), str_starts_with(), str_ends_with(), and sprintf(). Functions like str_contains() return a boolean and are case‑sensitive.
if (str_contains('PHPDevPro', 'Dev')) {
echo 'Found!';
}
Be careful with strlen() and other byte‑based functions when working with multibyte UTF‑8 characters. Use the mb_* functions (e.g., mb_strlen()) for correct character‑level handling, though they require the mbstring extension.
Numbers and Operators​
PHP uses int for whole numbers and float for decimals. Floating‑point arithmetic can produce tiny rounding errors, as in any language—use integers (cents) or the bcmath extension for high‑precision money calculations.
Operators include arithmetic (+, -, *, /, %, ** for exponentiation), assignment (=, +=, etc.), comparison (==, ===, !=, !==, <, >, <=, >=), and logical (&&, ||, !).
Null‑safe and null‑coalescing operators are essential for handling optional values:
// Null coalescing: returns the first defined non‑null value
$username = $_GET['user'] ?? 'guest';
// Null coalescing assignment: assigns only if the variable is null
$username ??= 'guest';
// Nullsafe operator (PHP 8.0+): calls a method only if the left side is not null
$length = $user?->getProfile()?->getName();
The spaceship operator <=> returns -1, 0, or 1 when comparing two values, making it handy for sorting.
$result = $a <=> $b; // -1 if $a < $b, 0 if equal, 1 if $a > $b
Parentheses can clarify complex expressions; don’t rely on operator precedence alone to communicate intent.
Arrays​
A PHP array is an ordered map—a data structure that associates keys to values. You can use it as a list (indexed array) or a dictionary (associative array).
$numbers = [10, 20, 30]; // indexed
$config = ['host' => 'localhost', 'port' => 5432]; // associative
Add elements with $numbers[] = 40;. Update or add a specific key with $config['db'] = 'postgres';. Remove with unset($array['key']);.
Since PHP 7.1, you can destructure arrays:
[$a, $b] = [1, 2]; // $a = 1, $b = 2
The foreach loop iterates over arrays:
foreach ($config as $key => $value) {
echo "$key: $value\n";
}
Common array functions:
count()– number of elementsarray_map()– apply a callback to each element and return a new arrayarray_filter()– filter elements with a callbackarray_reduce()– reduce array to a single valuearray_keys(),array_values()in_array()– check if a value exists (loose comparison by default; use third parametertruefor strict)array_key_exists()– check if a key existsisset($array['key'])– check if a key exists and is not null
The difference between array_key_exists() and isset() is important: isset() returns false if the value is null, while array_key_exists() returns true as long as the key exists, even with a null value. Similarly, in_array() uses loose comparison by default; always pass true for strict mode.
PHP arrays are flexible but can consume significant memory for large datasets. For processing huge collections, consider generators (yield) or dedicated data structures from extensions like ds.
Control Flow​
Conditionals and loops direct program execution. PHP offers several structures.
if / elseif / else – the classic branching tool.
match expression (PHP 8.0+) – a cleaner, safer alternative to switch. It uses strict comparison, returns a value, and throws an error if no arm matches (exhaustiveness).
$status = match($code) {
200, 201 => 'success',
404 => 'not found',
default => 'unknown',
};
switch – still present in older code, but match is preferred for new work because switch uses loose comparison and requires break to avoid fall‑through.
Loops:
for– counted iterationforeach– iterate arrays and objectswhile– condition‑firstdo‑while– condition‑lastbreakandcontinue– control loop flow
foreach ($items as $item) {
if ($item->isExpired()) {
continue;
}
process($item);
}
Use foreach for arrays and iterables; for numeric ranges, a for loop or array functions like array_map() work well.
Functions​
Functions encapsulate reusable logic. Modern PHP encourages fully typed signatures.
function calculateTotal(array $prices, float $taxRate): float {
$subtotal = array_sum($prices);
return $subtotal * (1 + $taxRate);
}
- Parameters can have default values (
float $taxRate = 0.2). - Named arguments (PHP 8.0+) allow you to pass arguments by name, improving readability and skipping defaults:
calculateTotal(prices: [10, 20], taxRate: 0.1). - Variadic parameters (
...$prices) capture a variable number of arguments into an array. - Return type declarations enforce the output type; use
: ?stringfor nullable,: int|floatfor union types (PHP 8.0+), and: Foo&Barfor intersection types (PHP 8.1+, mainly for interfaces).
Anonymous functions (closures) and arrow functions (PHP 7.4+) are useful for callbacks:
$doubled = array_map(fn($n) => $n * 2, [1, 2, 3]);
Arrow functions capture variables from the parent scope by value automatically.
Pass‑by‑reference is possible with &, but it can make code harder to follow. Prefer returning values over modifying arguments.
Global state inside functions should be minimized. If a function needs external data, pass it as a parameter. This makes testing and reasoning about the code much simpler, a principle we explore fully in Dependency Injection.
Nullability and Error-Prone Values​
PHP’s truthiness table can hide bugs. An empty string '', 0, "0", null, false, and an empty array are all falsy. A check like if (!$value) may inadvertently catch several of these when you only wanted to check for null or false.
function getConfigValue(string $key): mixed {
// ...
}
$value = getConfigValue('debug');
// Dangerous: will be true if $value is false, 0, '', etc.
if (!$value) {
// ...
}
// Better: explicit check
if ($value === null || $value === false) {
// ...
}
The null coalescing operator ?? and nullsafe operator ?-> reduce boilerplate when dealing with optional values. But always be explicit when meaning is important—don’t rely on implicit falsiness for business logic.
Exceptions and Error Handling​
PHP separates errors into two main branches: Exception (intended for application‑level problems) and Error (internal PHP errors like type errors). Both implement Throwable.
Throw an exception when your code encounters a condition it cannot recover from locally:
if ($amount <= 0) {
throw new \InvalidArgumentException('Amount must be positive.');
}
Catch exceptions with try/catch:
try {
processPayment($amount);
} catch (\InvalidArgumentException $e) {
// handle invalid input
} catch (\RuntimeException $e) {
// handle runtime failures
}
The finally block always executes, regardless of whether an exception was thrown.
Best practices:
- Catch only the specific exception types you can handle.
- Avoid catching
ThrowableorExceptionat the top level without a very good reason—it can hide critical errors. - Do not use exceptions for normal control flow; they are for exceptional situations.
- Let errors bubble up to an application boundary (e.g., a request handler) where they can be logged appropriately. Detailed logging and monitoring strategies are discussed in Logging and Observability.
Enums and Modern PHP Language Features​
PHP 8.1 introduced enumerations—a powerful way to define a limited set of possible values.
enum Status: string {
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
}
$status = Status::Published;
Backed enums (with : int or : string) let each case have a scalar equivalent. Enums can also have methods and implement interfaces.
Other modern features that improve fundamentals code:
- Readonly properties (PHP 8.1) and readonly classes (PHP 8.2) prevent modification after initialization.
- Constructor property promotion (PHP 8.0) reduces boilerplate by declaring and assigning properties in the constructor signature.
- Attributes (PHP 8.0) provide structured metadata instead of docblock annotations.
- First‑class callable syntax (
strlen(...)) (PHP 8.1) creates a closure from any callable.
These features are not just syntactic sugar; they help you express intent more clearly and reduce bugs.
A Complete Example​
The following example ties together many fundamentals: strict types, typed properties, an enum, a function with a union return type, array manipulation, and explicit error handling.
<?php
declare(strict_types=1);
enum Priority: string {
case Low = 'low';
case Medium = 'medium';
case High = 'high';
}
class Task {
public function __construct(
public readonly string $title,
public readonly Priority $priority,
public bool $completed = false,
) {}
}
function filterIncompleteTasks(array $tasks): array {
return array_filter(
$tasks,
fn(Task $task): bool => !$task->completed
);
}
function getHighestPriority(array $tasks): ?Task {
$priorities = [
Priority::High->value => 3,
Priority::Medium->value => 2,
Priority::Low->value => 1,
];
$max = null;
$highestTask = null;
foreach ($tasks as $task) {
$score = $priorities[$task->priority->value] ?? 0;
if ($max === null || $score > $max) {
$max = $score;
$highestTask = $task;
}
}
return $highestTask;
}
// Example usage
$tasks = [
new Task('Write article', Priority::High),
new Task('Fix bug', Priority::Medium, completed: true),
new Task('Review PR', Priority::High),
];
$incomplete = filterIncompleteTasks($tasks);
echo 'Incomplete tasks: ' . count($incomplete) . "\n";
$nextTask = getHighestPriority($incomplete);
echo 'Next up: ' . $nextTask?->title ?? 'Nothing to do';
This code uses declare(strict_types=1);, readonly classes, constructor promotion, an enum, typed functions, and safe null handling. It’s a tiny but representative example of how modern PHP fundamentals look in practice.
Common PHP Fundamentals Mistakes​
- Relying on loose comparisons –
==can hide type mismatches. Use===and!==unless you have a specific reason. - Ignoring return types – functions without declared return types are harder to understand and can silently return unexpected values.
- Mixing null, false, 0, and empty string – each has a distinct meaning; don’t treat them as interchangeable.
- Deeply nested conditionals – extract logic into separate functions or use early returns to improve readability.
- Overusing global variables – they make code unpredictable and untestable. Pass dependencies instead.
- Mutating arrays in place without thought – functions like
sort()modify the original array and returntrue;array_map()returns a new array. Know the difference. - Catching
ThrowableorExceptiontoo broadly – this masks critical errors. Catch specific exceptions. - Using outdated syntax – avoid
var(usepublic),array()long form (use[]), andswitchwhenmatchis clearer. - Omitting
declare(strict_types=1);in projects where type safety is valued – it’s a per‑file declaration that immediately catches type mistakes. - Treating PHP arrays like typed collections – an array can hold anything; for large or strictly typed data, consider dedicated value objects or typed properties inside classes.
PHP Fundamentals Checklist​
Use this list to verify your understanding:
- PHP files start with
<?phpand omit the closing?>tag. - I can explain variable naming, constants, and scope.
- I know the main PHP types and how to use type declarations.
- I prefer
===over==and understand why. - I can create and manipulate strings with common functions and understand encoding concerns.
- I use arrays as lists or maps and choose the right checking functions (
isset,array_key_exists,in_arraywith strict flag). - I can write
if,match,foreach, and other control structures clearly. - My functions have parameter and return type declarations, and I avoid modifying arguments by reference.
- I handle null safely with
??,?->, and explicit checks instead of relying on falsiness. - I throw and catch exceptions appropriately, catching only specific types.
- I can use modern PHP features like enums, readonly properties, and constructor promotion where they improve code.
Conclusion​
These language fundamentals are the foundation on which all other PHP engineering knowledge rests. Every concept covered here—from strict typing to array handling, from exception discipline to modern enumerations—will reappear as you progress into object‑oriented design, namespaces, Composer‑based projects, and full‑scale application architecture.
With these skills in place, you’re ready to explore Object‑Oriented Programming in PHP, learn how to organize code with Namespaces and Autoloading, and start managing dependencies with Composer. The clarity you gain now will make every subsequent topic easier and more practical.