PHP Language Basics
PHP is dynamically typed with a coercing engine: the type belongs to the value, not to the variable, and the conversion happens not at assignment but at the moment the value reaches an operator. The operator declares which type it needs, and the engine silently coerces the operand into it — '5' + 3 yields int(8), 5 . '3' yields '53', if ($x) coerces to bool. Hence the rule that separates a candidate who understands the language from one who memorised its syntax: declare(strict_types=1) does not turn juggling off — it only tightens the function boundary, the arguments and the return value, never the operators.
The other half of the topic is about what may be missing from a value. Four different constructs deal with null in PHP, and they diverge at exactly the point where you will get it wrong: isset cannot tell an absent key from a stored null, empty treats '0' as absence, and ?? inherits isset semantics. Let us name the traps upfront: PHP 8 fixed the most notorious comparison (0 == 'abc' is now false), yet '1' == '01' and '10' == '1e1' remain true — two numeric strings always compare as numbers; match compares with === while switch compares with ==; global does not copy a value, it binds a reference; include on a missing file lets execution continue, while require stops the script. The layer-by-layer breakdown is below.
Topic map
- Type juggling — where exactly the engine converts a value, what a numeric string is, and why
strict_typeshas no power over operators. - Comparison: == and === — the loose-comparison rules after PHP 8, identity for objects and arrays, and the
in_array-without-the-third-argument trap. - Working with null — how
isset,empty,is_nullandarray_key_existsdiverge, and what??,??=and?->do. - The match expression —
matchas an expression with strict comparison and no fall-through, plus thematch (true)idiom. - String literals — single versus double quotes, the interpolation syntaxes, heredoc versus nowdoc, and the indentation of the closing marker.
- References and & — by-value passing with copy-on-write, what
&actually does, why an object is not "passed by reference", and howforeach (&$v)corrupts an array. - Variable scope — every function gets its own scope,
globalas a reference into$GLOBALS,staticinside a function, and variable variables. - include and require — a warning versus a fatal error, what
_oncereally caches, and why application code should use an autoloader instead.
Common Mistakes and Traps
| Mistake | Consequence |
|---|---|
Expecting declare(strict_types=1) to stop '5' + 3 | Strict types govern the function boundary only — the operator still coerces the operand |
Treating '0' as truthy because the string is non-empty | '0' is falsy: a non-emptiness check will discard valid input |
Checking isset($a['k']) for a field that may legitimately be null | A stored null is indistinguishable from an absent key — you need array_key_exists |
Expecting ?? to substitute a fallback for 0 or '' | 0 and '' are set and not null — that is ?:, not ?? |
Using ?: on $_GET['page'] | Unlike ??, it does not silence the undefined-key warning |
Assuming ?-> also swallows a missing method | ?-> guards only against a null receiver; a missing method is still an Error |
Believing 0 == 'abc' is still true | In PHP 8 a non-numeric string is compared against the number's string form — it is false now |
Checking a token against a whitelist with in_array and no third argument | The comparison uses ==: in_array(0, ['0']) is true, and a foreign value gets through |
Expecting match to compare loosely | Arms are tested with ===: match ('1') will not hit an arm 1 |
Omitting default in a match | An uncovered value throws UnhandledMatchError in production |
Expecting $name to interpolate inside single quotes | The string is printed literally — no substitution happens |
| Indenting the closing heredoc marker differently from the body | The marker's indentation is stripped from every line — the whole string shifts, and a less-indented body is a ParseError |
| Believing objects are "passed by reference" | What is copied by value is the handle: mutating the object is visible outside, reassigning the parameter is not |
Forgetting unset($v) after foreach ($a as &$v) | The reference to the last element survives: the next foreach overwrites it on every iteration |
Expecting global $x to copy the value | global binds a reference to $GLOBALS['x'] — an assignment rewrites the global |
| Counting on implicit capture of the enclosing scope inside a function | PHP has none: values arrive as parameters, through use, or via global |
Expecting include to stop the script on a missing file | You get a warning and execution continues with incomplete state — require is what stops it |
Believing _once caches the file's result | It only remembers the resolved path and skips pulling it in again |
Interview relevance
The language basics are the first-round filter. What is probed is not definitions but your execution model: where the engine converts a value, what a variable actually holds, and what happens to it when it is passed into a function. A candidate who says "in PHP 8, == first asks whether the string is numeric, and only then decides whether to compare as numbers or as strings" is immediately apart from the one who answers "== compares without regard to type".
Typical checks:
- The difference between
==and===, and what PHP 8 changed. - The boundary
strict_typesgoverns — why it does not touch operators. - How
isset,empty,is_nullandarray_key_existsdiverge. - How
??differs from?:, and what??=does. - Why
matchis strict andswitchis not. - What is copied when you pass an array, and what when you pass an object.
- What
globaldoes and why a parameter is better. - How
includediffers fromrequire.
Common wrong answer: "isset checks that the variable exists." A half-truth that breaks people: isset also returns false for a stored null — so your "did the field arrive?" check silently discards a field that did arrive, carrying null. The second classic failure is "objects in PHP are passed by reference": what is copied by value is the handle, which is why $obj->x = 1 inside a function is visible outside while $obj = new Foo() is not.