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

<?php … ?>

The standard tags. <?php must be followed by whitespace (a space or a newline).

<?= … ?>

The short echo tag: <?= $x ?> is shorthand for <?php echo $x; ?>. Always available, independent of any setting.

<? … ?>

Short open tag. Only works when short_open_tag is On, which is not the case in the shipped ini files; do not use it.

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

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.

Names and case sensitivity

PHP’s case rules are a historical mix:

Kind of name Case-sensitive?

Variables ($title and $Title are different)

yes

Constants defined with const or define()

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 (if, IF, If), true, false, null

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

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.

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.

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 __construct or __get are reserved for the language’s magic.

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 <?php and <?= 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 are StudlyCaps, class constants UPPER_CASE, method names camelCase.

  • 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, use imports 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.

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 $author next to string $author adds nothing); use PHPDoc for what native types cannot say.

  • Use @deprecated for 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.