---
title: 'Syntax and Tags'
source: 'https://academia.sh/en/courses/php-fundamentals/syntax-and-tags'
course: 'PHP Fundamentals'
language: en
updated: '2026-10-21T05:03:33+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Shows with the exit code and the server response that a file with a syntax error stops before any line runs'
    - 'Tells an error thrown during execution apart from a syntax error by the presence of earlier output'
    - 'Mixes code and template with opening, closing and short echo tags'
    - 'Measures how a closing tag at the end of a file can add bytes to the output'
---

# Syntax and Tags

A file with a syntax error never starts running, how that differs from an error thrown during execution, and how code and template mix through tags in a PHP file.

The Execution Model lesson left two rows side by side in the core table: `halted execution` printed one line and exited with 255, while `syntax error` printed no line and exited with 255. Both belong to the halts channel, but they say two different things at once: one is the cutting of an execution that is already running, the other is an execution that could never start. This lesson measures the difference.

The difference comes from how PHP handles a file. Before the file is run, it is read in full and parsed according to the rules of the language. If parsing fails, not a single line of the file runs. Tags decide where a file begins and ends: PHP counts only the part between the opening and closing tags as code, and writes everything outside them to the output as it is. This lesson measures the two subjects together, because both concern when a file starts running.

## A File That Never Starts Running

Two small files carry the same two steps: first print a line, then run into a problem. In the first, the problem appears during execution: a `RuntimeException` is thrown and not caught. In the second, the problem is against the language itself: the statement `$x = ;` expects a value and meets a semicolon. The third file is sound and serves as the comparison. All three are given as files only, without output blocks.

```php
<?php
// fatal.php — prints a line first, then throws an uncaught exception
echo "before\n";
throw new RuntimeException('stop');
```

```php
<?php
// broken.php — syntax error: the statement $x = ; has no value
echo "before\n";
$x = ;
```

```php
<?php
// sound.php — valid syntax, prints only one line
echo "before\n";
```

The script below runs each file in a separate execution. `display_errors=0` keeps the error text out of the output; only the lines the file printed and the exit code remain. `php -l` only parses the file without running it and reports the exit code.

```bash
# separate.sh — the same two steps, two different stops: thrown during execution and failed in parsing
php -d display_errors=0 fatal.php; echo "fatal exit=$?"
php -d display_errors=0 broken.php; echo "broken exit=$?"
php -l sound.php >/dev/null 2>&1; echo "sound -l=$?"
php -l broken.php >/dev/null 2>&1; echo "broken -l=$?"
```

```
before
fatal exit=255
broken exit=255
sound -l=0
broken -l=255
```

**LE6.** Both files exited with 255, but `fatal.php` managed to print its first line and `broken.php` did not. What is measured is the presence of output, not the text of the error message: the message changes between versions and carries a file path. `php -l` gave 0 for the sound file and 255 for the broken one; that is, the parsing result can be learned without running the file at all.

The same difference turns into a status code on the built-in server. When `broken.php` is requested, the file cannot start running, so the server has no response body and the status code is `500`. `fatal.php` first prints the `before` line; the body has begun, and the error that comes afterward can no longer change the status code. The server starts with `-d display_errors=0`, so no error text appears in the body.

```bash
# server.sh — a file with a syntax error gives 500, a file that throws during execution gives 200 with a partial body
php -S 127.0.0.1:8081 -d display_errors=0 >/dev/null 2>&1 &
pid=$!
for i in $(seq 1 50); do curl -s -o /dev/null http://127.0.0.1:8081/sound.php && break; sleep 0.1; done
curl -s -w 'status=%{http_code}\n' http://127.0.0.1:8081/sound.php
curl -s -w 'status=%{http_code}\n' http://127.0.0.1:8081/fatal.php
curl -s -w 'status=%{http_code}\n' http://127.0.0.1:8081/broken.php
kill $pid
```

```
before
status=200
before
status=200
status=500
```

**LE7.** Three requests gave three different results: the sound file gave `before` and `200`, the file that failed during execution gave the same `before` line and still `200`, and the file with the syntax error gave `500` with no body. The `200` on the second line does not mean the error was lost; because the error came after the body had begun, it did not reach the status code. For this reason the status code is not a reliable measure of whether a problem was heard; it reflects only whether the error came before or after the output.

## Mixing Code and Template

A PHP file can carry code and plain text interleaved. The opening tag `<?php` starts a code section and the closing tag `?>` ends it; everything between the two is parsed, and everything outside goes to the output unchanged. `<?=` is the short form used to print a value: `<?= $product ?>` does the same job as `<?php echo $product; ?>`. In this way a file can be written as both a script and a template. The example below produces an HTML list with a loop over an array. The `foreach (...):` and `endforeach;` form is the notation meant for blocks that spread across tags instead of curly braces.

```php
<?php
// template.php — mixing code and template, with the short echo tag
$products = ['pencil', 'notebook', 'eraser'];
?>
<ul>
<?php foreach ($products as $product): ?>
  <li><?= $product ?></li>
<?php endforeach; ?>
</ul>
```

```
<ul>
  <li>pencil</li>
  <li>notebook</li>
  <li>eraser</li>
</ul>
```

**LE8.** A single newline directly after each `?>` tag is not written to the output; PHP counts it as part of the closing tag. For this reason the output has no blank lines left over from the code lines, and the three `<li>` lines stack one under another with no gap. This behavior holds only for a single newline; if two newlines follow the tag, the second one enters the output.

This result matters at the very end of a file. In a file that carries only PHP code, no closing tag is written; the file is valid without a closing tag for the opening tag. The reason is measurable: a newline or space that slips in after the closing tag enters the output. The script below writes the same one-line program in three forms and measures how many bytes each produces.

```bash
# closing.sh — a newline left after the closing tag at the end of a file adds bytes
printf '<?php\necho "a";\n' > open.php
printf '<?php\necho "a";\n?>\n' > closed1.php
printf '<?php\necho "a";\n?>\n\n' > closed2.php
for d in open closed1 closed2; do
  printf '%s %s\n' "$d" "$(php $d.php | wc -c | tr -d ' ')"
done
```

```
open 1
closed1 1
closed2 2
```

**LE9.** The file without a tag and the file with a closing tag followed by one newline produced one byte, the `echo "a"` itself; the file with a closing tag followed by two newlines produced two bytes. The harm of the extra byte is that it is invisible: every byte that enters the output before the response headers are sent makes setting headers too late. For this reason, if the file carries only code, the final tag is not written.

## A Line Comment Also Ends at the Closing Tag

Tags have one more consequence that they process before parsing: a closing tag ends the code section even inside a line comment. Everything after the tag, including the rest of the comment, is plain text; the following lines stop being code. No syntax error appears in this case, because there is no code left to parse, and the file runs without trouble.

```php
<?php
// comment.php — a closing tag inside a line comment ends the code section
echo "a\n"; // comment ?> this part is text
echo "b\n";
```

```
a
 this part is text
echo "b\n";
```

**LE10.** The output holds the `a` line, then the part of the comment after the tag, and finally the line `echo "b\n";` itself, that is, its source text, as plain text; `b` was never printed. There was no signal at all: no warning, no status code. This observation falls in the silent channel of the table and does not change this lesson's channel; it only shows that tags are processed before parsing, independently of the meaning of the comment.

## Case Sensitivity

In PHP, whether a name is sensitive to upper and lower case depends on the kind of name. Keywords and function names are insensitive; variable names and constant names defined with `define()` are sensitive. A typo is therefore not noticed in one place and noticed in another. The file below tries four names with upper and lower case differences; the error handler keeps a counter.

```php
<?php
// case.php — function names are case-insensitive, variable and constant names are sensitive
$notices = 0;
set_error_handler(function (int $level) use (&$notices): bool {
    $notices++;
    return true;
});

$name = 'Ada';
define('LIMIT', 5);

ECHO 'keyword: worked in uppercase too', "\n";
echo 'STRLEN:     ', STRLEN('abc'), "\n";
echo 'notices:    ', $notices, "\n";

$seen = $Name;
echo 'notices:    ', $notices, ' (after reading $Name)', "\n";
echo 'defined(limit): ', var_export(defined('limit'), true), "\n";
echo 'defined(LIMIT): ', var_export(defined('LIMIT'), true), "\n";
```

```
keyword: worked in uppercase too
STRLEN:     3
notices:    0
notices:    1 (after reading $Name)
defined(limit): false
defined(LIMIT): true
```

**LE11.** `ECHO` and `STRLEN` worked even though they were written in uppercase, and produced no notice. `$Name` is a variable separate from `$name`; when it was read, a warning was heard and the counter rose to one. In constant names too, `limit` and `LIMIT` are different: only the exact spelling was found. So the same typo is silent in a function name and a warning in a variable name; the kind of name that was misspelled decides which channel the error is heard through.

## Single and Double Quotes

A string is written in two forms in PHP, and the two are not the same. In a single-quoted string, variables and escape sequences are not interpreted; only `\'` and `\\` carry a special meaning. In a double-quoted string a variable is substituted and escape sequences such as `\n` are resolved. The file below writes the same text in both forms and counts their lengths.

```php
<?php
// strings.php — single quotes resolve neither variables nor escapes, double quotes do
$to = 'Ada';
$single = 'welcome $to\n';
$double = "welcome $to\n";
$braced = "welcome {$to}x";

echo 'single:  ', $single, "|\n";
echo 'double:  ', $double, "|\n";
echo 'braced:  ', $braced, "|\n";
echo 'single length: ', strlen($single), "\n";
echo 'double length: ', strlen($double), "\n";
echo 'backslash: ', strlen('a\\b'), ' ', strlen('a\nb'), ' ', strlen("a\nb"), "\n";
```

```
single:  welcome $to\n|
double:  welcome Ada
|
braced:  welcome Adax|
single length: 13
double length: 12
backslash: 3 4 3
```

**LE12.** The single-quoted string came out with `$to` and `\n` as they are, so its length was thirteen. In the double-quoted string `Ada` took the place of `$to` and `\n` became a single newline character. The curly braces decide where the variable name ends. The last line shows the difference at the character level: in single quotes `\n` is two characters, in double quotes one. Neither produces a notice; a `\n` written in single quotes silently fails to give the expected newline.

## Comment Forms

PHP recognizes three comment forms: `#` and `//` run to the end of the line, and `/* */` runs until its closing. Multi-line comments do not nest: the first `*/` ends the comment. The file below surrounds five `echo` statements with different comments.

```php
<?php
// comment_kinds.php — measures where each comment form ends
echo "1\n"; # line-end comment
echo "2\n"; // line-end comment
echo "3\n"; /* inline comment */ echo "4\n";
/* outer comment /* inner comment */ echo "5\n";
```

```
1
2
3
4
5
```

**LE13.** All five lines were printed. When `/*` opened a second time on the fourth line, the comment did not deepen; the first `*/` closed the comment and `echo "5\n"` ran as code. Code that relies on closing a block nested inside another runs whatever comes after the closing. This too is in the silent channel: there is no error or warning, only the amount of running code has grown.

## echo and print

PHP has two language constructs that write to the screen, and they are not the same. `echo` takes several values separated by commas and returns no value. `print` takes a single value and always returns `1`; for this reason it can be used inside an expression. The file below measures the two behaviors and the way an array element is written inside double quotes.

```php
<?php
// output.php — print returns a value, echo takes many arguments, an array element resolves in double quotes
$result = print "a\n";
echo 'print returned: ', $result, "\n";
echo 'one', 'two', "\n";

$items = [10, 20];
echo "array: $items[0]-$items[1]\n";
echo 'in single quotes: $items[0]', "\n";
```

```
a
print returned: 1
onetwo
array: 10-20
in single quotes: $items[0]
```

**LE14.** `print` wrote the line and returned `1`; `echo` wrote the two strings one after another with nothing between them. In the double-quoted string `$items[0]` was replaced by the value of the element, and in the single-quoted one it stayed as it was. None of these produces a notice; the difference can be seen only by looking at the output.

## The Bounding Measurement

The rule that a file with a syntax error never runs applies to each file separately. **LE15.** If a file pulls in another file with `include`, the faulty one is the pulled-in file and the main file has already started running. Because the pulled-in file is parsed during execution, the error is thrown at that point as a `ParseError`, and `try` with `catch` can catch it. The file below pulls in the `broken.php` written earlier.

```php
<?php
// main.php — a file with a syntax error is included; the main file has started running
echo "main started\n";
try {
    include 'broken.php';
} catch (ParseError $error) {
    echo 'caught ', get_class($error), "\n";
}
echo "main finished\n";
```

```
main started
caught ParseError
main finished
```

The main file printed all three of its lines, while the `before` line of `broken.php` did not appear: the pulled-in file again ran none of its own lines. The rule stayed the same and its scope changed: parsing is done per file, and the failure of one file reaches only as far as the catcher of the file that is pulling it in. If there is no catcher, the `ParseError` too falls into the halts channel.

## Summary

- The file is parsed in full before it runs; not a single line of a file with a syntax error runs.
- An error thrown during execution keeps the earlier output: `fatal.php` printed one line, `broken.php` printed none; both exited with 255.
- This lesson's channel is halts: a syntax error cuts execution before it starts, and an error thrown during execution cuts it after it has started.
- On the built-in server, a file with a syntax error gives `500` with no body; an error thrown during execution leaves partial output with `200` after the body has begun.
- The `?>` and `<?=` tags mix code and template; in a file that carries only code, no closing tag is written, because an extra newline after it adds bytes to the output.
- The rule is per file: a syntax error in an included file is thrown, after the main file has run, as a `ParseError` that can be caught.

## Next Step

It is now clear that the file started running and what is inside the tags. The Variables and Data Types lesson measures, inside the code, whether the type of a value changes without a signal: can a value lose its type?
