Basic Syntax and Style
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against PHP 8.5, checked on 2026-10-05. It covers what a PHP source file looks like at the lexical level — tags, statements, comments, names — and the community coding style that almost every modern PHP project follows.
PHP tags
The PHP parser only looks at code between an opening tag and a closing tag; everything outside is copied to the output unchanged.
| Tag | Meaning |
|---|---|
|
The standard tags. |
|
The short echo tag: |
|
Short open tag. Only works when |
ASP-style tags (<%, <%=) and the <script language="php"> tag were removed in PHP 7.0.
<?php
declare(strict_types=1);
$book = ['title' => 'Kindred', 'year' => 1979];
?>
<p>
<?= htmlspecialchars($book['title'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>
(<?= $book['year'] ?>)
</p>
<p>
Kindred (1979)
</p>
The output shows a subtle rule: PHP swallows one newline directly after a closing ?>. That is why Kindred and
(1979) end up on the same line, while the newline after ) (which is plain HTML, not a closing tag) is kept.
Source: PHP tags
Omit the closing tag in pure-PHP files
If a file contains only PHP code, leave out the closing ?>. Anything after it, even a single trailing newline or
space, is sent as output. In a web request, that output is sent before any header() or setcookie() call made later
in other files, which then fails with the infamous "Cannot modify header information - headers already sent"
warning. PSR-12 and PER Coding Style require omitting it.
<?php
declare(strict_types=1);
// src/Catalogue/Isbn.php -- no closing tag at the end of the file
function normaliseIsbn(string $isbn): string
{
return str_replace(['-', ' '], '', $isbn);
}
echo normaliseIsbn('978-0-00-000001-1'), "\n"; // 9780000000011
Source: PHP tags
Escaping from HTML
Because everything outside the tags is output, PHP can switch in and out of HTML, even in the middle of a control
structure. The alternative syntax (if (…): … endif;, foreach (…): … endforeach;) is designed for this
templating style:
<?php
declare(strict_types=1);
$books = ['Solaris', 'Kindred', 'The Dispossessed'];
$e = static fn (string $s): string => htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
?>
<?php if ($books === []): ?>
<p>The catalogue is empty.</p>
<?php else: ?>
<ul>
<?php foreach ($books as $title): ?>
<li><?= $e($title) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
When PHP meets a ?> in the middle of a block, it outputs the following HTML only if the enclosing condition is true:
this is more efficient than echo-ing large strings. Template code in plain PHP is covered in depth in
Templating, Routing and Middleware by Hand;
frameworks use dedicated template languages such as Laravel’s Blade.
Instruction separation
Statements end with a semicolon. The closing tag ?> implies a semicolon, so the last statement before it may omit
one — but write it anyway, because the tag might move.
Blocks are delimited by braces, and whitespace (spaces, tabs, newlines) is insignificant between tokens, except inside
strings and after <?php.
<?php
declare(strict_types=1);
$a = 1; $b = 2; // two statements on one line: legal but discouraged
$sum = $a
+ $b; // one statement over two lines
echo $sum, "\n"; // 3
Source: Instruction separation
Comments
PHP supports three comment styles. Doc comments, which start with /**, are a convention on top of block comments:
they are attached to the following declaration and exposed by Reflection (getDocComment()), which is why tools such
as PHPStan, Psalm and IDEs read them.
<?php
declare(strict_types=1);
// A single-line comment, C++ style
# A single-line comment, shell style (avoid: see below)
/*
* A block comment. It cannot be nested:
* the first "*" "/" sequence ends it.
*/
/**
* A doc comment, attached to the function below.
*
* @param list<string> $titles
*/
function longestTitle(array $titles): ?string
{
// A single-line comment ends at the end of the line or at the first closing tag on that line.
return array_reduce($titles, fn (?string $carry, string $t): string => strlen($t) > strlen($carry ?? '') ? $t : $carry);
}
echo longestTitle(['Solaris', 'Kindred', 'The Dispossessed']), "\n"; // The Dispossessed
$doc = (new ReflectionFunction('longestTitle'))->getDocComment();
echo strtok($doc, "\n"), "\n"; // /**
A // or # comment stops at a closing tag: writing ?> inside such a comment switches back to HTML mode and the
rest of the line is output, so never put a literal closing tag in a single-line comment.
Since PHP 8.0, #[ starts an attribute rather than a comment, so a
line such as #[Test] that once was a harmless comment is now code. Prefer // for single-line comments.
Source: Comments and ReflectionFunctionAbstract::getDocComment
Names and case sensitivity
PHP’s case rules are a historical mix:
| Kind of name | Case-sensitive? |
|---|---|
Variables ( |
yes |
Constants defined with |
yes |
Property names, array keys |
yes |
Functions, methods, classes, interfaces, traits, enums, namespaces |
no (but write them exactly as declared: autoloaders map class names to case-sensitive file paths) |
Keywords ( |
no |
An identifier starts with a letter, an underscore or a byte from 0x80 to 0xff, followed by letters, digits,
underscores or such bytes, so $título is a valid variable name in a UTF-8 file. Variables always start with $.
<?php
declare(strict_types=1);
function shelve(string $title): string { return "Shelved: {$title}"; }
echo SHELVE('Solaris'), "\n"; // functions are case-insensitive: Shelved: Solaris
$title = 'a';
$Title = 'b';
echo $title, $Title, "\n"; // ab -- variables are case-sensitive
echo TRUE === true ? "same\n" : "different\n"; // same
Source: Variables — Basics and User-defined functions
Reserved keywords and other reserved words
Keywords are language constructs (if, function, class, match, fn,
readonly, …). They cannot be used as names of constants, classes or functions, but since PHP 7.0 they are allowed
as names of methods, properties and class constants (except class as a constant name), because the lexer is
context-sensitive:
<?php
declare(strict_types=1);
final class Shelf
{
public const LIST = 'list'; // "list" is a keyword, but fine as a class constant name
public function list(): array // ... and as a method name
{
return ['Solaris', 'Kindred'];
}
public function new(): self { return new self(); }
}
print_r((new Shelf())->new()->list());
echo Shelf::LIST, "\n";
The full keyword list (from __halt_compiler, abstract, and, array, as through xor, yield and
yield from) is in the manual’s appendix; recent additions are fn (7.4), match (8.0) and readonly (8.1, still
usable as a function name).
The other reserved words cannot be used as names of classes, interfaces, traits or enums, because they name types:
int, float, bool, string, true, false, null, void (7.1), iterable (7.1), object (7.2), mixed
(8.0), never (8.1), and, as of PHP 8.5, array and callable, plus self, parent and static. The words enum,
resource and numeric are soft reserved: still legal as class names, but discouraged because they may become types.
Upcoming in PHP 8.6, using let, is and readonly as identifiers in contexts that a future version wants to reserve
is deprecated.
Source: List of Keywords and List of other reserved words
Predefined names
PHP also predefines constants (PHP_VERSION, PHP_EOL, PHP_INT_MAX, E_ALL, …), the compile-time "magic"
constants (__LINE__, __FILE__, __DIR__, __FUNCTION__, __CLASS__, __TRAIT__, __METHOD__, __PROPERTY__
(8.4) and __NAMESPACE__), and classes such as stdClass, Closure, Generator and the exception hierarchy. They
are listed in Variables, Scope and Constants and
the manual’s reserved-words appendix.
Source: Predefined Classes and Magic constants
Naming: the userland naming guide
The manual’s userland naming guide gives two rules for code you publish:
-
PHP owns the top-level (global) namespace: it may add any function, class or constant there in a future version. Put your code in your own namespace (
Acme\Catalogue\Book), as Namespaces and Autoloading explains. -
Do not start your own names with a double underscore (
__): names like__constructor__getare reserved for the language’s magic.
Source: Userland Naming Guide and Rules
Coding style: PSR-1 and PER Coding Style
PHP itself enforces almost no layout, so the community standardised it through the PHP-FIG (PHP Framework Interop Group). Two documents matter:
-
PSR-1, Basic Coding Standard: files use only
<?phpand<?=tags and UTF-8 without a byte-order mark; a file either declares symbols (classes, functions, constants) or causes side effects (output, ini changes), not both; namespaces and classes follow PSR-4 autoloading; class names areStudlyCaps, class constantsUPPER_CASE, method namescamelCase. -
PER Coding Style 3.0 (the "PHP Evolving Recommendation" that replaced PSR-12, itself the successor of PSR-2): 4 spaces of indentation, Unix line endings, no closing tag,
useimports grouped by kind (classes, then functions, then constants), braces on their own line for classes and methods but on the same line for control structures, a space after control keywords,declare(strict_types=1);right after the opening tag (separated by a blank line), and rules for the newer syntax (attributes, enums,match, property hooks, short closures). Unlike a PSR, a PER is updated as the language evolves.
A file following both looks like this:
<?php
declare(strict_types=1);
namespace Library\Catalogue;
use DateTimeImmutable;
use InvalidArgumentException;
final class Loan
{
public const int MAX_DAYS = 21;
public function __construct(
public readonly string $isbn,
public readonly DateTimeImmutable $borrowedAt,
) {
}
public function dueAt(): DateTimeImmutable
{
return $this->borrowedAt->modify('+' . self::MAX_DAYS . ' days');
}
public function isOverdue(DateTimeImmutable $now): bool
{
if ($now < $this->borrowedAt) {
throw new InvalidArgumentException('The loan cannot end before it starts.');
}
return $now > $this->dueAt();
}
}
$loan = new Loan('978-0-00-000001-1', new DateTimeImmutable('2026-09-01'));
echo $loan->dueAt()->format('Y-m-d'), "\n"; // 2026-09-22
var_dump($loan->isOverdue(new DateTimeImmutable('2026-10-05'))); // bool(true)
You do not apply these rules by hand: PHP_CodeSniffer (phpcs/phpcbf), PHP-CS-Fixer and Laravel Pint check and
fix them automatically. See PSR Standards and
Coding Style.
Source: PSR-1: Basic Coding Standard and PER Coding Style
PHPDoc comments
PHP’s native types cannot express everything: array shapes, lists of a given type, generics, or which exceptions a function throws. PHPDoc, a convention originating with phpDocumentor, fills that gap with tags in doc comments. Static analysers (PHPStan, Psalm) and IDEs read them; PHP itself ignores them.
<?php
declare(strict_types=1);
/**
* Finds the books written by one author.
*
* @param list<array{title: string, author: string, year: int}> $books
* @param non-empty-string $author
*
* @return list<string> The matching titles, in catalogue order.
*
* @throws InvalidArgumentException When $author is blank.
*
* @see https://www.php.net/manual/en/function.array-filter.php
*/
function titlesBy(array $books, string $author): array
{
if (trim($author) === '') {
throw new InvalidArgumentException('Author must not be blank.');
}
return array_values(array_map(
fn (array $b): string => $b['title'],
array_filter($books, fn (array $b): bool => $b['author'] === $author),
));
}
print_r(titlesBy([
['title' => 'Kindred', 'author' => 'Octavia E. Butler', 'year' => 1979],
['title' => 'Solaris', 'author' => 'Stanisław Lem', 'year' => 1961],
['title' => 'Dawn', 'author' => 'Octavia E. Butler', 'year' => 1987],
], 'Octavia E. Butler'));
Guidelines that most projects follow:
-
Do not repeat in PHPDoc what a native type already says (
@param string $authornext tostring $authoradds nothing); use PHPDoc for what native types cannot say. -
Use
@deprecatedfor documentation, but the native#[\Deprecated]attribute (8.4, see Attributes) when callers should receive a deprecation notice at runtime. -
Generic types (
@template,list<T>,array<K, V>) are covered in Static Analysis and Refactoring.
Where older books and tutorials are outdated
-
Coding standards. Books from 2018 cite PSR-2 (and sometimes the PEAR coding standard). PSR-2 was deprecated in favour of PSR-12 in 2019, and PSR-12 is now superseded by PER Coding Style, currently version 3.0.
-
Comments with
#. Old code used#freely; since PHP 8.0, a#directly followed by[starts an attribute. -
Tags. ASP tags and
<script language="php">were removed in PHP 7.0; short open tags<?are off by default. -
Keywords as method names. Pre-7.0 books state that keywords can never be used as names; since 7.0 they are allowed for class members.