php
✓Verified·Scanned 2/18/2026
Write solid PHP avoiding type juggling traps, array quirks, and common security pitfalls.
from clawhub.ai·v1f77f28·4.3 KB·0 installs
Scanned from 1.0.0 at 1f77f28 · Transparency log ↗
$ vett add clawhub.ai/ivangdavila/php
Type Juggling Traps
==coerces types:"0" == falseis true — always use===for strict comparison"10" == "10.0"is true — string comparison converts to numbers if both look numeric0 == "any string"was true before PHP 8 — still beware legacy codein_array($val, $arr)uses loose comparison — passtrueas third param for strictswitchuses loose comparison — use match expression in PHP 8+ for strict
Array Gotchas
array_merge()reindexes numeric keys — use+operator to preserve keys$arr[] = $valappends,$arr[0] = $valreplaces — different behaviors- Unset array element doesn't reindex — use
array_values()after unset if needed empty([])is true,empty("0")is true — usecount()or=== []for clarityforeachon reference:foreach ($arr as &$val)— unset$valafter loop or last ref persists
Null Handling
isset()returns false for null — usearray_key_exists()to check if key exists??null coalescing doesn't trigger on false or empty string — only null/undefined?->nullsafe operator (PHP 8) — chain methods on potentially null objects$obj?->method()returns null if obj is null — no error thrownis_null($x)vs$x === null— identical, but===is faster
String Pitfalls
- Double quotes interpolate:
"$var"— single quotes literal:'$var' - Heredoc interpolates like double quotes — nowdoc (with single quote) doesn't
strpos()returns 0 for match at start — use=== falsenot!strpos()- String offset access
$str[0]works — but$str[-1]only works PHP 7.1+ - Multibyte:
strlen()counts bytes — usemb_strlen()for UTF-8 char count
Variable Scope
- Functions don't see outer variables — pass explicitly or use
globalkeyword - Closures need
use ($var)to capture outer variables — by value unlessuse (&$var) staticin function persists value across calls — useful for caching$thisnot available in static methods — useself::orstatic::- Superglobals (
$_GET,$_POST) available everywhere — but don't trust them
OOP Quirks
- Objects pass by reference-like handle — clone explicitly with
clone $obj __clone()called after shallow clone — implement for deep clone of nested objectsprivatenot accessible in child — useprotectedfor inheritancestatic::late binding vsself::early binding —staticrespects overrides- Constructor not called on unserialization — implement
__wakeup()for init
Error Handling
@suppresses errors — avoid, makes debugging impossibletry/catchonly catches exceptions — errors needset_error_handler- PHP 7+ throws
Errorfor fatal — catchThrowablefor both Error and Exception finallyalways runs — even if exception thrown or return in tryset_exception_handler()for uncaught — last resort logging
Security Essentials
- Never concatenate SQL — use prepared statements with PDO or mysqli
htmlspecialchars()output in HTML — prevents XSS, useENT_QUOTES- Validate
$_GET/$_POSTbefore use —filter_input()or explicit validation - Session fixation: regenerate ID after login —
session_regenerate_id(true) - CSRF: verify token on state-changing requests — store in session, check on submit
Date/Time
- Always set timezone:
date_default_timezone_set()— or php.inidate.timezone DateTimeis mutable — useDateTimeImmutableto avoid side effectsstrtotime()relative to now or second param — "next monday" depends on current day- Comparing DateTime objects works with
<>==— but===checks identity - Store UTC in database — convert to local timezone on display
Performance Traps
count()in loop condition recalculates — cache:$len = count($arr)array_push($arr, $val)slower than$arr[] = $val— use[]for single- Long-running scripts: unset large variables —
gc_collect_cycles()if needed - Include files:
require_oncehas lookup overhead — use autoloading - String concatenation in loop — use array and
implode()for many pieces