Features
VHP supports a comprehensive subset of PHP syntax and semantics.
Basic Syntax
- PHP tags:
<?php,?>,<?=(short echo) echostatement with comma-separated expressions- String literals (single/double quoted) with escape sequences
- Integer, float, boolean, and null literals
- Comments:
//,/* */,# - HTML passthrough (mixed PHP/HTML)
Variables & Assignment
<?php
$name = "VHP";
$count = 42;
$count += 8; // Compound assignment
echo "$name: $count"; // Output: VHP: 50
Supported Assignment Operators
- Basic:
= - Compound:
+=,-=,*=,/=,%=,.=
Operators
Arithmetic
<?php
echo 2 + 3 * 4; // 14 (correct precedence!)
echo 2 ** 10; // 1024 (power operator)
Comparison
<?php
echo 1 == "1" ? "loose" : "strict"; // loose
echo 1 === "1" ? "loose" : "strict"; // strict
Supported: ==, ===, !=, !==, <, >, <=, >=, <=> (spaceship)
Logical Operators
<?php
$a = true && false; // false
$b = true || false; // true
$c = !true; // false
$d = true and false; // false (lower precedence)
$e = true or false; // true (lower precedence)
$f = true xor true; // false
Supported: &&, ||, !, and, or, xor
Null Coalescing
<?php
$user = $name ?? "Anonymous";
Ternary
<?php
echo $age >= 18 ? "adult" : "minor";
Increment/Decrement
<?php
$i = 0;
echo ++$i; // 1 (pre-increment)
echo $i++; // 1 (post-increment, $i is now 2)
Control Flow
If-Elseif-Else
<?php
$score = 85;
if ($score >= 90) {
echo "A";
} elseif ($score >= 80) {
echo "B";
} else {
echo "C";
}
While Loop
<?php
$i = 0;
while ($i < 5) {
echo $i++;
}
For Loop
<?php
for ($i = 0; $i < 5; $i++) {
echo $i;
}
Do-While Loop
<?php
$i = 0;
do {
echo $i++;
} while ($i < 3);
Switch Statement
<?php
$day = 1;
switch ($day) {
case 1:
echo "Monday";
break;
case 2:
echo "Tuesday";
break;
default:
echo "Other day";
}
Break and Continue
<?php
for ($i = 0; $i < 10; $i++) {
if ($i == 3) continue; // Skip 3
if ($i == 7) break; // Stop at 7
echo $i;
}
Arrays
Array Literals
<?php
$numbers = [1, 2, 3, 4, 5];
$mixed = ["hello", 42, true, null];
$empty = [];
Associative Arrays
<?php
$person = [
"name" => "John",
"age" => 30,
"city" => "NYC"
];
echo $person["name"]; // John
Array Access and Modification
<?php
$arr = [10, 20, 30];
echo $arr[0]; // 10
$arr[1] = 25; // Modify
$arr[] = 40; // Append
Foreach Loop
<?php
$colors = ["red", "green", "blue"];
// Value only
foreach ($colors as $color) {
echo $color . "\n";
}
// Key and value
$prices = ["apple" => 1.50, "banana" => 0.75];
foreach ($prices as $fruit => $price) {
echo "$fruit: \$$price\n";
}
Array First/Last (PHP 8.5)
<?php
$arr = [10, 20, 30, 40];
echo array_first($arr); // 10
echo array_last($arr); // 40
// Works with associative arrays
$assoc = ['a' => 1, 'b' => 2, 'c' => 3];
echo array_first($assoc); // 1
echo array_last($assoc); // 3
// Empty array returns null
var_dump(array_first([])); // NULL
PHP-Compatible Type Coercion
<?php
// Loose equality with type juggling
echo 0 == "0" ? "yes" : "no"; // yes
echo 0 == "" ? "yes" : "no"; // yes
echo 0 == false ? "yes" : "no"; // yes
// Strict equality (no coercion)
echo 0 === "0" ? "yes" : "no"; // no
echo 0 === false ? "yes" : "no"; // no
Functions
User-Defined Functions
<?php
function greet($name) {
return "Hello, " . $name . "!";
}
echo greet("World"); // Hello, World!
Default Parameters
<?php
function power($base, $exp = 2) {
return $base ** $exp;
}
echo power(3); // 9
echo power(2, 10); // 1024
Recursive Functions
<?php
function factorial($n) {
if ($n <= 1) return 1;
return $n * factorial($n - 1);
}
echo factorial(5); // 120
Variadic Functions
Variadic functions accept a variable number of arguments using the ... operator:
<?php
function sum(...$numbers) {
$total = 0;
foreach ($numbers as $n) {
$total += $n;
}
return $total;
}
echo sum(1, 2, 3); // 6
echo sum(1, 2, 3, 4, 5); // 15
// Mix regular and variadic parameters
function greet($greeting, ...$names) {
foreach ($names as $name) {
echo $greeting . ", " . $name . "!\n";
}
}
greet("Hello", "Alice", "Bob", "Charlie");
// Hello, Alice!
// Hello, Bob!
// Hello, Charlie!
Argument Unpacking
Use ... to unpack arrays into function arguments:
<?php
function add($a, $b, $c) {
return $a + $b + $c;
}
$numbers = [1, 2, 3];
echo add(...$numbers); // 6
// Works with built-in functions too
$values = [3, 1, 4, 1, 5];
echo max(...$values); // 5
Built-in Functions
Note: is_readable and is_writable only check file existence.
Classes & Objects
Class Declaration
<?php
class Person {
public $name;
public $age = 0;
function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
function greet() {
return "Hello, my name is " . $this->name;
}
}
Creating Objects
<?php
$person = new Person("Alice", 30);
echo $person->name; // Alice
echo $person->greet(); // Hello, my name is Alice
Properties
<?php
class Box {
public $value = 10; // With default value
public $label; // Without default (null)
}
$box = new Box();
echo $box->value; // 10
$box->value = 20; // Modify property
Visibility Modifiers
VHP supports visibility modifiers for properties and methods:
public- Accessible from anywhere (default)protected- Accessible from class and subclassesprivate- Accessible only within the class
Constructors
<?php
class Rectangle {
public $width;
public $height;
function __construct($w, $h) {
$this->width = $w;
$this->height = $h;
}
function area() {
return $this->width * $this->height;
}
}
$rect = new Rectangle(10, 5);
echo $rect->area(); // 50
Constructor Property Promotion (PHP 8.0)
Shorthand syntax for declaring and initializing properties directly in the constructor:
<?php
class User {
public function __construct(
public $name,
private $age
) {}
public function getAge() {
return $this->age;
}
}
$user = new User("Alice", 30);
echo $user->name; // Alice
echo $user->getAge(); // 30
You can mix promoted and regular parameters:
<?php
class Product {
public function __construct(
public $name,
public $price,
$currency = "USD"
) {
echo "Price: $price $currency";
}
}
$p = new Product("Widget", 9.99); // Price: 9.99 USD
echo $p->name; // Widget
Static Method Calls
<?php
class Math {
function square($n) {
return $n * $n;
}
}
echo Math::square(5); // 25
Static Properties (PHP 5.0+)
Static properties are class-level variables shared across all instances of a class. They are accessed using the class name or special keywords like self::, parent::, or static::.
Basic Static Property
<?php
class Counter {
public static $count = 0;
}
Counter::$count = 5;
echo Counter::$count; // 5
Static Property with Visibility
<?php
class Config {
private static $debug = false;
protected static $token = "abc";
public static $api_key = "xyz";
}
echo Config::$api_key; // xyz
Accessing Static Properties within Class Methods
Use self::$property to access static properties from within class methods:
<?php
class Counter {
public static $count = 0;
public function increment() {
self::$count++;
}
public static function getCount() {
return self::$count;
}
}
$a = new Counter();
$b = new Counter();
$a->increment();
$b->increment();
echo Counter::getCount(); // 2
Static Property Inheritance
Child classes inherit parent static properties but have separate storage:
<?php
class Base {
protected static $value = "base";
public static function getValue() {
return self::$value;
}
}
class Child extends Base {
protected static $value = "child";
}
echo Base::getValue(); // base
echo Child::getValue(); // child
Late Static Binding (PHP 5.3+)
Use static::$property for late static binding, which refers to the called class rather than the defined class:
<?php
class Animal {
protected static $type = "Animal";
public static function getType() {
return static::$type; // LSB: refers to called class
}
}
class Dog extends Animal {
protected static $type = "Dog";
}
echo Animal::getType(); // Animal
echo Dog::getType(); // Dog
Difference Between self:: and static::
self:: refers to the class where the method is defined, while static:: refers to the class that was called (late static binding):
<?php
class Base {
protected static $name = "Base";
public static function withSelf() {
return self::$name; // Always "Base"
}
public static function withStatic() {
return static::$name; // Called class's value
}
}
class Child extends Base {
protected static $name = "Child";
}
echo Child::withSelf(); // Base
echo Child::withStatic(); // Child
Readonly Static Properties (PHP 8.3+)
Static properties can be declared as readonly:
<?php
class Config {
public static readonly $version = "1.0.0";
}
echo Config::$version; // 1.0.0
Config::$version = "2.0.0"; // Error: Cannot modify readonly property
Array Operations on Static Properties
Static properties can hold arrays and support array operations:
<?php
class Collection {
public static $items = [];
}
Collection::$items[] = "a";
Collection::$items[] = "b";
echo count(Collection::$items); // 2
Multiple Objects
<?php
class Counter {
public $count = 0;
function increment() {
$this->count = $this->count + 1;
}
}
$a = new Counter();
$b = new Counter();
$a->increment();
$a->increment();
$b->increment();
echo $a->count . ", " . $b->count; // 2, 1
Match Expressions (PHP 8.0)
Match expressions are a more powerful alternative to switch statements. They return a value and use strict comparison.
Basic Match
<?php
$food = "apple";
$result = match($food) {
"apple" => "fruit",
"carrot" => "vegetable",
"chicken" => "meat",
default => "unknown",
};
echo $result; // fruit
Multiple Conditions
<?php
$num = 2;
$result = match($num) {
1, 2, 3 => "low",
4, 5, 6 => "medium",
default => "high",
};
echo $result; // low
Strict Comparison
Match uses strict (===) comparison, unlike switch which uses loose (==):
<?php
$val = "1";
echo match($val) {
1 => "integer",
"1" => "string",
}; // string
Match with Expressions
<?php
$grade = 85;
echo match(true) {
$grade >= 90 => "A",
$grade >= 80 => "B",
$grade >= 70 => "C",
default => "F",
}; // B
Named Arguments (PHP 8.0)
Named arguments allow you to pass arguments to functions based on parameter names, making code more readable and allowing you to skip optional parameters.
Basic Named Arguments
<?php
function greet($name, $greeting = "Hello") {
return "$greeting, $name!";
}
echo greet(name: "Leo"); // Hello, Leo!
echo greet(greeting: "Hi", name: "World"); // Hi, World!
Skipping Optional Parameters
<?php
function configure($host, $port, $debug = false, $verbose = false) {
echo "Host: $host, Port: $port";
if ($debug) echo ", Debug: on";
if ($verbose) echo ", Verbose: on";
}
// Can skip parameters in the middle
configure(host: "localhost", port: 8080, verbose: true);
// Output: Host: localhost, Port: 8080, Verbose: on
Any Argument Order
<?php
function format($color, $size, $bold = false) {
return "Color: $color, Size: $size";
}
// Arguments can be in any order when named
echo format(size: 14, bold: true, color: "red");
With Methods and Constructors
<?php
class Point {
public $x;
public $y;
public function __construct($x = 0, $y = 0) {
$this->x = $x;
$this->y = $y;
}
}
// Works with constructors
$p = new Point(y: 5, x: 3);
// Works with method calls
class Calculator {
public function add($a, $b) {
return $a + $b;
}
}
$calc = new Calculator();
echo $calc->add(b: 10, a: 5); // 15
Mixing Positional and Named Arguments
<?php
function build($type, $size, $color = "white") {
return "$type, $size, $color";
}
// Positional arguments come first, then named
echo build("box", 10, color: "blue");
Interfaces
Interfaces define contracts that classes must follow, specifying method signatures without implementations.
Basic Interface
<?php
interface Drawable {
function draw();
function getColor();
}
class Circle implements Drawable {
function draw() {
echo "Drawing a circle";
}
function getColor() {
return "red";
}
}
$shape = new Circle();
$shape->draw(); // Drawing a circle
Multiple Interfaces
Classes can implement multiple interfaces:
<?php
interface Drawable {
function draw();
}
interface Resizable {
function resize($scale);
}
class Rectangle implements Drawable, Resizable {
function draw() {
echo "Drawing rectangle";
}
function resize($scale) {
echo "Resizing by $scale";
}
}
Interface Inheritance
Interfaces can extend other interfaces:
<?php
interface Shape {
function area();
}
interface Colorable {
function getColor();
}
interface ColoredShape extends Shape, Colorable {
function render();
}
class Square implements ColoredShape {
function area() {
return 100;
}
function getColor() {
return "blue";
}
function render() {
echo "Rendering colored shape";
}
}
Interface Constants
Interfaces can define constants:
<?php
interface Config {
const VERSION = "1.0";
const MAX_SIZE = 100;
}
echo Config::VERSION; // 1.0
Abstract Classes and Methods
Abstract classes provide a way to define base classes that cannot be instantiated directly. They can contain both abstract methods (without implementations) that must be implemented by child classes, and concrete methods with full implementations.
Basic Abstract Class
<?php
abstract class Animal {
abstract function speak();
function describe() {
echo "I am an animal";
}
}
class Dog extends Animal {
function speak() {
echo "Woof!";
}
}
$dog = new Dog();
$dog->speak(); // Woof!
$dog->describe(); // I am an animal
Cannot Instantiate Abstract Classes
Attempting to instantiate an abstract class directly results in an error:
<?php
abstract class Shape {
abstract function area();
}
$shape = new Shape(); // Error: Cannot instantiate abstract class Shape
Multiple Abstract Methods
Abstract classes can have multiple abstract methods:
<?php
abstract class Repository {
abstract function find($id);
abstract function save($entity);
abstract function delete($id);
function count() {
return 0; // Default implementation
}
}
class UserRepository extends Repository {
function find($id) {
return "User $id";
}
function save($entity) {
echo "Saving $entity";
}
function delete($id) {
echo "Deleting $id";
}
}
Abstract Classes with Constructors
Abstract classes can have constructors that are called when child classes are instantiated:
<?php
abstract class Entity {
public $id;
function __construct($id) {
$this->id = $id;
}
abstract function getName();
}
class User extends Entity {
public $name;
function __construct($id, $name) {
$this->id = $id;
$this->name = $name;
}
function getName() {
return $this->name;
}
}
$user = new User(1, "John");
echo $user->getName(); // John
Key Points
- Abstract classes cannot be instantiated directly
- Abstract methods have no body (no
{}) - Non-abstract child classes must implement all abstract methods
- Abstract classes can contain both abstract and concrete methods
- Abstract methods can only exist in abstract classes
Asymmetric Visibility (PHP 8.4)
Asymmetric visibility allows different visibility modifiers for reading and writing properties. This enables the common pattern of public read access but restricted write access without requiring explicit getter/setter methods.
Syntax
<?php
class ClassName {
public private(set) $property; // Public read, private write
public protected(set) $property; // Public read, protected write
protected private(set) $property; // Protected read, private write
}
The syntax is: <read-visibility> <write-visibility>(set) $property
Basic Usage
<?php
class User {
public private(set) string $name;
public function __construct(string $name) {
$this->name = $name; // OK: write inside class
}
}
$user = new User("Alice");
echo $user->name; // OK: public read
$user->name = "Bob"; // Error: Cannot modify private property
Public Read, Protected Write
<?php
class Base {
public protected(set) int $count = 0;
}
class Child extends Base {
public function increment() {
$this->count++; // OK: protected write in subclass
}
}
$child = new Child();
echo $child->count; // OK: public read (0)
$child->increment();
echo $child->count; // OK: public read (1)
$child->count = 5; // Error: Cannot modify protected property
Protected Read, Private Write
<?php
class Base {
protected private(set) string $status = "pending";
private function updateStatus(string $newStatus) {
$this->status = $newStatus; // OK: private write inside class
}
}
class Child extends Base {
public function getStatus() {
return $this->status; // OK: protected read in subclass
}
public function setStatus(string $s) {
$this->status = $s; // Error: Cannot modify private property
}
}
With Static Properties
Asymmetric visibility works with static properties:
<?php
class Config {
public private(set) static int $version = 1;
public static function upgrade() {
self::$version++; // OK: write inside class
}
}
echo Config::$version; // OK: public read (1)
Config::upgrade();
echo Config::$version; // OK: public read (2)
Config::$version = 5; // Error: Cannot modify private property
Multiple Properties
Classes can have multiple properties with different asymmetric visibility:
<?php
class Data {
public private(set) int $id;
public protected(set) string $name;
public int $value; // Symmetric visibility
public function __construct(int $id, string $name, int $value) {
$this->id = $id;
$this->name = $name;
$this->value = $value;
}
}
$data = new Data(1, "test", 42);
echo $data->id; // OK: public read
echo $data->name; // OK: public read
echo $data->value; // OK: public read
$data->value = 100; // OK: public write
$data->id = 2; // Error: Cannot modify private property
$data->name = "x"; // Error: Cannot modify protected property (outside class)
With Inheritance
Write visibility is inherited and respected across the class hierarchy:
<?php
class Base {
public protected(set) string $status = "pending";
}
class Child extends Base {
public function approve() {
$this->status = "approved"; // OK: protected write in subclass
}
}
$child = new Child();
echo $child->status; // pending
$child->approve();
echo $child->status; // approved
$child->status = "x"; // Error: Cannot modify protected property
Validation Rules
The write visibility must be equal to or more restrictive than the read visibility:
Valid combinations:
public private(set)✓ (most common)public protected(set)✓protected private(set)✓
Invalid combinations:
private public(set)✗ (write cannot be less restrictive)private protected(set)✗protected public(set)✗public public(set)✗ (redundant, just usepublic)private private(set)✗ (redundant, just useprivate)
Incompatibilities
Asymmetric visibility cannot be combined with:
Readonly properties:
<?php
class User {
public private(set) readonly string $name; // Error
}
// Error: Readonly properties cannot have asymmetric visibility
Both features solve the same problem (restricting writes), so they’re mutually exclusive.
Property hooks with set:
<?php
class Temperature {
public private(set) float $celsius { // Error
set {
$this->celsius = $value;
}
}
}
// Error: Properties with set hooks cannot have asymmetric visibility
If you need custom set logic, use property hooks instead of asymmetric visibility.
Use Cases
Asymmetric visibility is ideal for:
- Immutable public data - Properties that should be publicly readable but only internally modifiable
- Controlled state changes - Properties that can only be modified through specific methods
- Counter/statistics - Public counters that can only be incremented internally
- Configuration - Public config that can only be set during initialization
- IDs and timestamps - Values that should never be changed after creation
Notes
- Asymmetric visibility is purely a visibility control mechanism
- It doesn’t affect property initialization in constructors
- Works with constructor property promotion
- Compatible with attributes
- Works with both instance and static properties
- Write visibility is checked at runtime based on the current class context
Final Classes and Methods
The final keyword prevents classes from being extended and methods from being overridden.
Final Classes
A final class cannot be extended by any other class:
<?php
final class Singleton {
public function getValue() {
return 42;
}
}
$obj = new Singleton();
echo $obj->getValue(); // 42
// class Extended extends Singleton {} // Error!
Cannot Extend Final Classes
Attempting to extend a final class results in an error:
<?php
final class Base {}
class Child extends Base {} // Error: cannot extend final class Base
Final Methods
A final method cannot be overridden by child classes:
<?php
class Base {
final public function locked() {
return "locked";
}
public function unlocked() {
return "base";
}
}
class Child extends Base {
// Cannot override locked()!
public function unlocked() {
return "child";
}
}
$c = new Child();
echo $c->locked(); // locked
echo $c->unlocked(); // child
Cannot Override Final Methods
Attempting to override a final method results in an error:
<?php
class Base {
final public function noOverride() {
return "final";
}
}
class Child extends Base {
public function noOverride() {} // Error: Cannot override final method
}
Final Static Methods
Final can be combined with static:
<?php
class Util {
final public static function helper() {
return "helped";
}
}
echo Util::helper(); // helped
Key Points
- Final classes cannot be extended by any class
- Final methods cannot be overridden by child classes
finalandabstractcannot be used together- Private methods can technically be final but it’s redundant
- Constructors can be final (prevents child from changing construction)
Readonly Properties (PHP 8.1)
Readonly properties can only be assigned once and cannot be modified afterward. They’re useful for immutable data structures.
Basic Readonly Properties
<?php
class Point {
public readonly $x;
public readonly $y;
public function __construct($x, $y) {
$this->x = $x; // Assignment in constructor is allowed
$this->y = $y;
}
}
$p = new Point(10, 20);
echo $p->x; // 10
$p->x = 30; // Error: Cannot modify readonly property
Constructor Property Promotion with Readonly
<?php
class User {
public function __construct(
public readonly string $id,
public readonly string $email
) {}
}
$user = new User("123", "user@example.com");
echo $user->id; // 123
// Cannot modify $user->id or $user->email
Property Hooks (PHP 8.4)
Property hooks allow you to define custom logic for getting and setting property values using get and set hooks. This enables computed properties, validation, and side effects without explicit getter/setter methods.
Get Hook (Expression Syntax)
Use a get hook to compute property values dynamically.
Syntax:
<?php
public type $property {
get => expression;
}
Example:
<?php
class Circle {
public float $radius = 5.0;
public float $diameter {
get => $this->radius * 2;
}
}
$c = new Circle();
echo $c->diameter; // 10 (computed from radius)
$c->radius = 10.0;
echo $c->diameter; // 20 (automatically updated)
Set Hook (Block Syntax)
Use a set hook to validate, transform, or trigger side effects when setting a property. The incoming value is available via the special $value variable.
Syntax:
<?php
public type $property {
set {
// Custom logic here
// $value contains the incoming value
}
}
Example:
<?php
class Temperature {
private float $celsius = 0;
public float $fahrenheit {
get => $this->celsius * 9/5 + 32;
set {
$this->celsius = ($value - 32) * 5/9;
}
}
}
$t = new Temperature();
$t->fahrenheit = 212;
echo $t->fahrenheit; // 212
Combined Get and Set Hooks
Properties can have both get and set hooks to create a complete abstraction.
Example:
<?php
class User {
private string $rawPassword = "";
public string $password {
get => "***REDACTED***";
set {
$this->rawPassword = hash('sha256', $value);
echo "Password updated securely";
}
}
}
$user = new User();
$user->password = "secret123"; // Password updated securely
echo $user->password; // ***REDACTED***
Side Effects with Set Hooks
Set hooks can trigger logging, validation, or other side effects.
Example:
<?php
class Logger {
private array $log = [];
public string $message {
set {
$this->log[] = $value;
echo "Logged: " . $value;
}
}
}
$logger = new Logger();
$logger->message = "Hello"; // Logged: Hello
Key Points
- Expression syntax (
get => expr) for simple computed properties - Block syntax (
set { ... }) for complex set logic - $value variable available in set hooks with the incoming value
- Computed properties don’t need backing storage
- Validation can be performed in set hooks
- Side effects like logging can be triggered on property access
- Type hints work with property hooks
- Visibility modifiers (public, private, protected) apply to the hooks
Limitations
- No get-only writeable: Get hooks make properties read-only by default
- Set hooks require explicit backing storage if you want to store the value
- Hooks cannot be abstract or in interfaces (PHP 8.4 limitation)
Readonly Classes (PHP 8.2)
Readonly classes make all properties implicitly readonly without needing to mark each property individually.
Basic Readonly Class
<?php
readonly class Point {
public function __construct(
public $x,
public $y
) {}
}
$p = new Point(1.5, 2.5);
echo $p->x; // 1.5
$p->x = 3.0; // Error: Cannot modify readonly property
Readonly Class with Explicit Properties
<?php
readonly class User {
public $name;
private $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
public function getAge() {
return $this->age;
}
}
$user = new User("John", 30);
echo $user->name; // John
echo $user->getAge(); // 30
$user->name = "Jane"; // Error: Cannot modify readonly property
Key Differences from Explicit Readonly Properties
- All properties are implicitly readonly (no need for
readonlykeyword on each property) - Properties cannot have explicit
readonlymodifier (redundant) - More concise for immutable classes
- All visibility modifiers work (public, protected, private)
Object Cloning
Basic Clone (PHP 5.0+)
The clone operator creates a shallow copy of an object. All property values are copied to the new object, but nested objects are copied by reference.
Syntax:
<?php
$cloned = clone $original;
Example:
<?php
class Point {
public function __construct(
public float $x,
public float $y
) {}
}
$p1 = new Point(1.0, 2.0);
$p2 = clone $p1;
echo $p1->x; // 1.0
echo $p2->x; // 1.0
$p2->x = 3.0;
echo $p1->x; // 1.0 (original unchanged)
echo $p2->x; // 3.0
Notes:
- Creates a shallow copy (nested objects are shared by reference)
- Original object remains unchanged when modifying cloned object’s properties
- Works with all classes and objects
Clone With (PHP 8.4+)
The clone with syntax creates a copy while modifying specific properties in a single expression. This is especially useful for immutable objects with readonly properties.
Syntax:
<?php
$cloned = clone $original with {
property1: value1,
property2: value2,
};
Example:
<?php
readonly class ImmutablePoint {
public function __construct(
public float $x,
public float $y
) {}
}
$p1 = new ImmutablePoint(1.0, 2.0);
$p2 = clone $p1 with { x: 3.0 };
echo $p1->x; // 1.0
echo $p1->y; // 2.0
echo $p2->x; // 3.0
echo $p2->y; // 2.0 (unchanged)
Multiple Properties:
<?php
$p1 = new ImmutablePoint(1.0, 2.0);
$p2 = clone $p1 with { x: 3.0, y: 4.0 };
echo $p2->x; // 3.0
echo $p2->y; // 4.0
With Expressions:
Property values can be any expression, including references to the original object:
<?php
$p1 = new Point(10.0, 5.0);
$p2 = clone $p1 with {
x: $p1->x * 2.0,
y: $p1->y + 10.0
};
echo $p2->x; // 20.0
echo $p2->y; // 15.0
With Readonly Properties:
Clone with allows re-initialization of readonly properties in the cloned object:
<?php
class User {
public function __construct(
public readonly string $id,
public readonly string $email
) {}
}
$user1 = new User("123", "old@example.com");
$user2 = clone $user1 with { email: "new@example.com" };
echo $user2->email; // new@example.com
Notes:
- At least one property modification is required
- Modified properties must exist on the object
- Trailing commas are allowed:
clone $obj with { x: 1, } - Works seamlessly with readonly classes and properties
- Property values are evaluated at clone time
Shallow Copy Behavior
Both clone and clone with perform shallow copies. If an object contains references to other objects, those references are copied (not the objects themselves):
<?php
class Inner {
public function __construct(public int $value) {}
}
class Outer {
public function __construct(public Inner $inner) {}
}
$inner = new Inner(10);
$o1 = new Outer($inner);
$o2 = clone $o1;
$o2->inner->value = 20;
echo $o1->inner->value; // 20 (affected by change to clone)
echo $o2->inner->value; // 20
Magic Methods
Magic methods are special methods that PHP calls automatically in certain situations. They enable custom behavior for common operations like string conversion, property access, and method calls.
__toString
Called when an object is used in a string context (echo, concatenation, etc.).
<?php
class User {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function __toString(): string {
return $this->name;
}
}
$user = new User("Alice");
echo $user; // Alice
echo "Hello, " . $user; // Hello, Alice
__invoke
Called when an object is used as a function.
<?php
class Adder {
private $base;
public function __construct($base) {
$this->base = $base;
}
public function __invoke($n) {
return $this->base + $n;
}
}
$add5 = new Adder(5);
echo $add5(10); // 15
__get and __set
Called when accessing or setting undefined/inaccessible properties.
<?php
class MagicProps {
private $data = [];
public function __get($name) {
return $this->data[$name] ?? "undefined";
}
public function __set($name, $value) {
$this->data[$name] = $value;
}
}
$obj = new MagicProps();
$obj->foo = "bar";
echo $obj->foo; // bar
echo $obj->missing; // undefined
__isset and __unset
Called when isset() or unset() is used on undefined/inaccessible properties.
<?php
class Container {
private $items = [];
public function __set($name, $value) {
$this->items[$name] = $value;
}
public function __isset($name) {
return isset($this->items[$name]);
}
public function __unset($name) {
unset($this->items[$name]);
}
}
$c = new Container();
$c->key = "value";
echo isset($c->key) ? "yes" : "no"; // yes
unset($c->key);
echo isset($c->key) ? "yes" : "no"; // no
__call and __callStatic
Called when invoking undefined/inaccessible methods.
<?php
class Wrapper {
public function __call($method, $args) {
return "Called $method with " . count($args) . " args";
}
public static function __callStatic($method, $args) {
return "Static: $method";
}
}
$w = new Wrapper();
echo $w->unknownMethod(1, 2, 3); // Called unknownMethod with 3 args
echo Wrapper::anything(); // Static: anything
Supported Magic Methods
| Method | Purpose | Status |
|---|---|---|
__construct |
Object initialization | Implemented |
__toString |
String conversion | Implemented |
__invoke |
Callable objects | Implemented |
__get |
Property read overloading | Implemented |
__set |
Property write overloading | Implemented |
__isset |
isset() overloading | Implemented |
__unset |
unset() overloading | Implemented |
__call |
Method call overloading | Implemented |
__callStatic |
Static method call overloading | Implemented |
__clone |
Clone behavior | Implemented |
Traits
Traits enable code reuse in single inheritance languages by allowing methods to be shared across multiple classes.
Basic Trait
<?php
trait Logger {
function log($message) {
echo "[LOG] $message\n";
}
}
class Application {
use Logger;
function run() {
$this->log("Application started");
}
}
$app = new Application();
$app->run(); // [LOG] Application started
Multiple Traits
Classes can use multiple traits:
<?php
trait Timestampable {
function timestamp() {
return "2024-01-01";
}
}
trait Identifiable {
function getId() {
return 42;
}
}
class Entity {
use Timestampable, Identifiable;
}
$entity = new Entity();
echo $entity->getId(); // 42
Trait Properties
Traits can define properties that become part of the using class:
<?php
trait Counter {
public $count = 0;
function increment() {
$this->count = $this->count + 1;
}
}
class Session {
use Counter;
}
$session = new Session();
$session->increment();
echo $session->count; // 1
Overriding Trait Methods
Class methods override trait methods:
<?php
trait DefaultBehavior {
function greet() {
return "Hello from trait";
}
}
class CustomClass {
use DefaultBehavior;
function greet() {
return "Hello from class";
}
}
$obj = new CustomClass();
echo $obj->greet(); // Hello from class
Traits Using Other Traits
Traits can use other traits:
<?php
trait A {
function methodA() {
echo "A";
}
}
trait B {
use A;
function methodB() {
echo "B";
}
}
class MyClass {
use B;
}
$obj = new MyClass();
$obj->methodA(); // A (inherited through trait B)
$obj->methodB(); // B
Conflict Resolution
When multiple traits define the same method, conflicts must be resolved:
<?php
trait A {
function conflict() {
echo "From A";
}
}
trait B {
function conflict() {
echo "From B";
}
}
// This would cause an error without resolution:
// class MyClass {
// use A, B; // Error: conflict() defined in both traits
// }
// Use 'insteadof' to resolve conflicts
class MyClass {
use A, B {
A::conflict insteadof B;
}
}
$obj = new MyClass();
$obj->conflict(); // From A
Method Aliasing
Create aliases for trait methods:
<?php
trait Greeter {
function greet() {
echo "Hello";
}
}
class Welcome {
use Greeter {
greet as sayHello;
}
}
$w = new Welcome();
$w->greet(); // Hello
$w->sayHello(); // Hello
Attributes (PHP 8.0)
Attributes provide a way to add structured metadata to declarations. VHP currently supports parsing attribute syntax and storing them in the AST.
Basic Attribute Syntax
<?php
#[Route("/api/users")]
class UserController {
public function index() {
echo "Users list";
}
}
$controller = new UserController();
$controller->index(); // Users list
Attributes with Arguments
Attributes can accept both positional and named arguments:
<?php
// Positional arguments
#[Route("/posts", "GET")]
class PostController {
public function list() {
echo "Posts";
}
}
// Named arguments
#[Route(path: "/users", method: "POST")]
class UserController {
public function create() {
echo "Create user";
}
}
// Mixed arguments
#[Cache(3600, driver: "redis")]
class DataService {
public function fetch() {
echo "Fetching data";
}
}
Multiple Attributes
Classes, methods, properties, and functions can have multiple attributes:
<?php
// Multiple attributes on separate lines
#[Deprecated]
#[Replaced(by: "UserServiceV2")]
interface UserService {
function getUsers();
}
// Multiple attributes in single brackets
#[Route("/admin"), Authenticated, RateLimit(100)]
class AdminController {
public function dashboard() {
echo "Admin dashboard";
}
}
Attributes on Different Declarations
Attributes can be applied to various language constructs:
<?php
// On classes
#[Entity(table: "users")]
class User {}
// On interfaces
#[Deprecated]
interface OldInterface {}
// On traits
#[Internal]
trait HelperMethods {}
// On methods
class Controller {
#[Route("/profile")]
public function profile() {
echo "Profile";
}
}
// On properties
class Model {
#[Column(type: "string", length: 255)]
public $name;
}
// On functions
#[Pure]
function calculate($x) {
return $x * 2;
}
// On parameters (including constructor promotion)
class Point {
public function __construct(
#[Positive] public $x,
#[Positive] public $y
) {}
}
Runtime Attribute Reflection
VHP provides built-in functions to retrieve attributes at runtime:
<?php
#[Route("/api/users")]
class UserController {
#[ValidateRequest]
public function create(#[FromBody] $data) {}
}
// Get class attributes
$class_attrs = get_class_attributes("UserController");
// Returns: [["name" => "Route", "arguments" => [["name" => null, "value" => "/api/users"]]]]
// Get method attributes
$method_attrs = get_method_attributes("UserController", "create");
// Get parameter attributes
$param_attrs = get_method_parameter_attributes("UserController", "create", "data");
Available Reflection Functions:
| Function | Description |
|---|---|
get_class_attributes($class) |
Get all attributes for a class |
get_method_attributes($class, $method) |
Get all attributes for a method |
get_property_attributes($class, $property) |
Get all attributes for a property |
get_function_attributes($function) |
Get all attributes for a function |
get_parameter_attributes($function, $param) |
Get all attributes for a function parameter |
get_method_parameter_attributes($class, $method, $param) |
Get all attributes for a method parameter |
get_interface_attributes($interface) |
Get all attributes for an interface |
get_trait_attributes($trait) |
Get all attributes for a trait |
Return Format:
Each function returns an array of attributes. Each attribute is an associative array:
<?php
[
"name" => "AttributeName",
"arguments" => [
[
"name" => "param_name", // null for positional args
"value" => "param_value"
]
]
]
Current Implementation Status
VHP fully supports:
- ✅ Parsing attribute syntax
- ✅ Storing attributes in the AST
- ✅ All attribute argument forms (positional, named, mixed)
- ✅ Attributes on all declarations (classes, methods, properties, functions, parameters, etc.)
- ✅ Attribute reflection API for runtime retrieval
Enums (PHP 8.1)
Enums (Enumerations) provide a way to define a type with a fixed set of possible values. VHP supports both pure enums (cases without values) and backed enums (cases backed by int or string values).
Pure Enums
Pure enums define named cases without scalar backing values.
Syntax:
<?php
enum EnumName {
case CaseName;
case AnotherCase;
}
Example:
<?php
enum Status {
case Pending;
case Active;
case Archived;
}
$status = Status::Active;
echo $status->name; // Active
Key Points:
- Cases have no backing values
- Access the case name via the
->nameproperty - No
->valueproperty available
Backed Enums (Int)
Backed enums with integer values provide both name and value properties.
Syntax:
<?php
enum EnumName: int {
case CaseName = value;
}
Example:
<?php
enum Priority: int {
case Low = 1;
case Medium = 5;
case High = 10;
}
$priority = Priority::High;
echo $priority->name; // High
echo $priority->value; // 10
Backed Enums (String)
Backed enums with string values work similarly to int-backed enums.
Syntax:
<?php
enum EnumName: string {
case CaseName = 'value';
}
Example:
<?php
enum Color: string {
case Red = 'red';
case Green = 'green';
case Blue = 'blue';
}
$color = Color::Red;
echo $color->name; // Red
echo $color->value; // red
Built-in Enum Methods
All enums have access to built-in methods for introspection and validation.
cases()
Returns an array of all enum cases.
<?php
enum Status {
case Pending;
case Active;
case Archived;
}
$cases = Status::cases();
echo count($cases); // 3
echo $cases[0]->name; // Pending
echo $cases[1]->name; // Active
Works with: Pure and backed enums
from()
Retrieves an enum case by its backing value. Throws an error if the value is not found.
<?php
enum Priority: int {
case Low = 1;
case Medium = 5;
case High = 10;
}
$priority = Priority::from(5);
echo $priority->name; // Medium
// Error: Value not found
$invalid = Priority::from(99); // Error: Value '99' is not a valid backing value
Works with: Backed enums only (int or string) Throws: Error if value not found
tryFrom()
Retrieves an enum case by its backing value. Returns null if the value is not found (safe version of from()).
<?php
enum Priority: int {
case Low = 1;
case Medium = 5;
case High = 10;
}
$priority = Priority::tryFrom(5);
echo $priority->name; // Medium
$invalid = Priority::tryFrom(99);
var_dump($invalid); // NULL
Works with: Backed enums only (int or string)
Returns: Enum case or null if not found
Enum Case Properties
Enum cases expose properties for introspection:
| Property | Available On | Description |
|---|---|---|
->name |
All enums | String name of the case |
->value |
Backed enums only | Backing value (int or string) |
Example:
<?php
enum Status {
case Pending;
}
enum Priority: int {
case Low = 1;
}
$status = Status::Pending;
echo $status->name; // Pending
// $status->value; // Error: Pure enum case doesn't have 'value' property
$priority = Priority::Low;
echo $priority->name; // Low
echo $priority->value; // 1
Using Enums
In Variables
<?php
enum Status {
case Active;
case Inactive;
}
$current = Status::Active;
echo $current->name; // Active
In Arrays
<?php
enum Status {
case Pending;
case Active;
case Archived;
}
$statuses = [Status::Pending, Status::Active];
echo $statuses[0]->name; // Pending
echo $statuses[1]->name; // Active
In Switch Statements
<?php
enum Status {
case Pending;
case Active;
case Archived;
}
$status = Status::Active;
switch ($status->name) {
case "Pending":
echo "Waiting";
break;
case "Active":
echo "Running";
break;
case "Archived":
echo "Done";
break;
}
// Output: Running
In Comparisons
<?php
enum Status {
case Pending;
case Active;
}
$s1 = Status::Active;
$s2 = Status::Active;
$s3 = Status::Pending;
var_dump($s1 === $s2); // bool(true)
var_dump($s1 === $s3); // bool(false)
Enum Validation
VHP enforces strict validation rules for enums:
Pure Enums:
- Cannot have case values
- Must have at least one case
- Case names must be unique
Backed Enums:
- Must declare backing type (
: intor: string) - All cases must have values matching the backing type
- Backing values must be unique
- Must have at least one case
Error Examples:
<?php
// Error: Pure enum cannot have case values
enum Status {
case Pending = 1; // Error
}
// Error: Backed enum must have case values
enum Priority: int {
case Low; // Error: missing value
}
// Error: Wrong backing type
enum Color: int {
case Red = "red"; // Error: string value for int-backed enum
}
// Error: Duplicate values
enum Level: int {
case Low = 1;
case Medium = 1; // Error: duplicate value
}
Notes
- Case Sensitivity: Enum names are case-insensitive (like classes), but case names are case-sensitive (
Status::Active≠Status::ACTIVE) - Type Safety: Enum cases don’t automatically coerce to other types
- String Representation: When converted to string, displays as
EnumName::CaseName - Boolean Context: All enum cases are truthy
- Comparison: Use strict comparison (
===) to compare enum cases
#[\Override] Attribute (PHP 8.3)
The #[\Override] attribute explicitly marks methods that override parent class, interface, or trait methods. The PHP engine validates that the method actually overrides something at class definition time, catching typos and refactoring errors.
Basic Usage
<?php
class Animal {
public function makeSound() {
return "...";
}
}
class Dog extends Animal {
#[\Override]
public function makeSound() {
return "Woof!";
}
}
$dog = new Dog();
echo $dog->makeSound(); // Woof!
Why Use #[\Override]?
Without #[\Override] - Silent failure:
<?php
class Animal {
public function makeSound() {
return "...";
}
}
class Cat extends Animal {
// Typo! Should be makeSound, but no error
public function makeSounds() {
return "Meow!";
}
}
$cat = new Cat();
echo $cat->makeSound(); // "..." (wrong - uses parent method)
With #[\Override] - Caught at definition time:
<?php
class Bird extends Animal {
#[\Override]
public function makeSounds() { // Error immediately!
return "Tweet!";
}
}
// Error: Bird::makeSounds has #[\Override] attribute, but no matching parent method exists
Validating Interface Methods
The #[\Override] attribute works with interface implementation:
<?php
interface Drawable {
public function draw();
}
class Circle implements Drawable {
#[\Override]
public function draw() {
return "Drawing circle";
}
}
$circle = new Circle();
echo $circle->draw(); // Drawing circle
Validating Trait Methods
Works with trait composition:
<?php
trait Greetable {
public function greet() {
return "Hello";
}
}
class Person {
use Greetable;
#[\Override]
public function greet() {
return "Hi there!";
}
}
$person = new Person();
echo $person->greet(); // Hi there!
Validating Abstract Methods
Implementing abstract methods counts as overriding:
<?php
abstract class Shape {
abstract public function area();
}
class Circle extends Shape {
private $radius = 5;
#[\Override]
public function area() {
return 3.14 * $this->radius * $this->radius;
}
}
$circle = new Circle();
echo $circle->area(); // 78.5
Inheritance Chain Validation
#[\Override] validates against the entire parent hierarchy:
<?php
class GrandParent {
public function legacy() {
return "old";
}
}
class Parent extends GrandParent {
// Doesn't override legacy()
}
class Child extends Parent {
#[\Override]
public function legacy() {
return "new"; // Valid: overrides GrandParent::legacy()
}
}
$child = new Child();
echo $child->legacy(); // new
Multiple Interfaces
Works with multiple interface implementations:
<?php
interface A {
public function foo();
}
interface B {
public function bar();
}
class C implements A, B {
#[\Override]
public function foo() {
return "foo";
}
#[\Override]
public function bar() {
return "bar";
}
}
$c = new C();
echo $c->foo() . $c->bar(); // foobar
Case Insensitivity
Both the attribute name and method name matching are case-insensitive:
<?php
class Base {
public function Method() {
return "base";
}
}
class Child extends Base {
#[\override] // lowercase attribute works
public function method() { // case-insensitive method match
return "child";
}
}
$child = new Child();
echo $child->method(); // child
Error Cases
No parent method:
<?php
class Standalone {
#[\Override]
public function test() {
return "fail";
}
}
// Error: Standalone::test has #[\Override] attribute, but no matching parent method exists
Typo in method name:
<?php
class Animal {
public function speak() {
return "...";
}
}
class Dog extends Animal {
#[\Override]
public function bark() { // Error: doesn't override speak()
return "Woof!";
}
}
// Error: Dog::bark has #[\Override] attribute, but no matching parent method exists
Override Sources
A method marked with #[\Override] must exist in one of:
- Parent class (any ancestor in the hierarchy)
- Implemented interface (any interface or parent interface)
- Used trait (any trait or nested trait)
Key Points
- Validation timing: Checked at class definition time, not at method call time
- Case insensitive: Both attribute name and method names are case-insensitive
- Works with: Parent classes, interfaces, traits, abstract methods, magic methods
- Error format:
ClassName::methodName has #[\Override] attribute, but no matching parent method exists - Static methods: Can use
#[\Override]on static method overrides - Constructor: Can use
#[\Override]on__constructif parent has it - PHP 8.3 feature: Part of the PHP 8.3 release
Use Cases
- Refactoring safety: Catch method name changes in parent classes
- API contract: Document that a method is meant to override parent behavior
- Typo prevention: Detect typos in override method names at definition time
- Interface compliance: Ensure interface methods are actually implemented
- Team coordination: Make override intent explicit for code reviewers
Pipe Operator (PHP 8.5)
The pipe operator (|>) enables functional-style function chaining, making data transformations more readable by flowing left-to-right.
Basic Usage
Instead of nesting function calls:
<?php
$result = strtoupper(trim(" hello "));
echo $result; // HELLO
Use the pipe operator:
<?php
$text = " hello ";
$result = $text |> trim(...) |> strtoupper(...);
echo $result; // HELLO
With Additional Arguments
The piped value is inserted as the first argument, with additional arguments following:
<?php
$text = "hello world";
$result = $text |> substr(..., 0, 5) |> strtoupper(...);
echo $result; // HELLO
Multiple Transformations
Chain multiple functions for complex data transformations:
<?php
$text = " HELLO WORLD ";
$result = $text
|> trim(...)
|> strtolower(...)
|> ucfirst(...);
echo $result; // Hello world
With User-Defined Functions
Works seamlessly with custom functions:
<?php
function double($x) {
return $x * 2;
}
function addTen($x) {
return $x + 10;
}
$result = 5 |> double(...) |> addTen(...);
echo $result; // 20 (5 * 2 + 10)
Benefits
- Readability: Operations flow in the order they’re applied (left-to-right)
- Composability: Easy to add or remove transformation steps
- Functional Style: Enables point-free programming patterns
- Clarity: Avoids deeply nested function calls
Technical Details
- Precedence: Lower precedence than most operators but higher than assignment
- Associativity: Left-associative, so
a |> b |> cevaluates as(a |> b) |> c - Placeholder: The
...syntax indicates where the piped value is inserted (always as the first argument) - Function Types: Works with built-in functions and user-defined functions
Example: Traditional vs. Pipe
Traditional nested calls:
<?php
$result = ucfirst(strtolower(trim(" HELLO WORLD ")));
With pipe operator:
<?php
$result = " HELLO WORLD "
|> trim(...)
|> strtolower(...)
|> ucfirst(...);
Both produce the same output ("Hello world"), but the pipe version is more readable and easier to modify.
Fibers (PHP 8.1)
Fibers provide cooperative multitasking with full-stack, interruptible functions. Unlike generators, Fibers can be suspended from anywhere in the call stack and maintain their own execution context.
Core Concepts
- Full-stack interruption: Can suspend from deeply nested function calls
- Cooperative multitasking: Explicit suspend/resume control
- Own call stack: Each Fiber maintains independent execution state
Basic Usage
<?php
function fiberFunction() {
echo "Start of fiber\n";
$value = Fiber::suspend("suspended");
echo "Resumed with: " . $value . "\n";
return "fiber_result";
}
$fiber = new Fiber('fiberFunction');
// Start the fiber - runs until suspend
$suspendedValue = $fiber->start();
echo "Fiber suspended with: " . $suspendedValue . "\n";
// Resume with a value
$result = $fiber->resume("resume_data");
echo "Fiber returned: " . $result . "\n";
Output:
Start of fiber
Fiber suspended with: suspended
Resumed with: resume_data
Fiber returned: fiber_result
Fiber API
Constructor
$fiber = new Fiber(callable $callback);
Control Methods
$fiber->start(mixed ...$args): mixed // Start execution
$fiber->resume(mixed $value = null): mixed // Resume from suspension
$fiber->getReturn(): mixed // Get return value after termination
Status Methods
$fiber->isStarted(): bool // Has the fiber been started?
$fiber->isSuspended(): bool // Is currently suspended?
$fiber->isTerminated(): bool // Has execution completed?
Static Methods
Fiber::suspend(mixed $value = null): mixed // Suspend current fiber
Fiber::getCurrent(): ?Fiber // Get currently running fiber
State Management
<?php
function testFunction() {
return 42;
}
$fiber = new Fiber('testFunction');
// Before starting
echo $fiber->isStarted() ? "true" : "false"; // false
echo $fiber->isSuspended() ? "true" : "false"; // false
echo $fiber->isTerminated() ? "true" : "false"; // false
$result = $fiber->start();
// After completion
echo $fiber->isStarted() ? "true" : "false"; // true
echo $fiber->isSuspended() ? "true" : "false"; // false
echo $fiber->isTerminated() ? "true" : "false"; // true
Current Implementation Limitations
VHP’s Fiber implementation provides the core API and basic functionality:
- ✅ Basic Fiber creation and execution
- ✅ State checking methods (
isStarted,isSuspended,isTerminated) - ✅
Fiber::getCurrent()andFiber::suspend() - ✅ Return value handling with
getReturn() - ⚠️ Suspend/resume is MVP-limited - Full call-stack suspension requires additional runtime support
The current implementation covers the essential Fiber API and enables basic cooperative multitasking patterns. Advanced suspend/resume scenarios may require additional development.
Exception Handling (PHP 8.0)
VHP provides comprehensive exception handling with try/catch/finally blocks, throw statements and expressions, and support for exception inheritance.
Basic Try/Catch
The simplest form of exception handling uses try to wrap potentially failing code and catch to handle exceptions.
Syntax:
<?php
try {
// Code that may throw an exception
} catch (ExceptionType $variable) {
// Handle the exception
}
Example:
<?php
try {
throw new Exception("Something went wrong");
} catch (Exception $e) {
echo "Caught: " . $e->getMessage();
}
// Output: Caught: Something went wrong
Exception Class
The base Exception class provides methods to retrieve information about the exception.
Available Methods:
getMessage()- Returns the exception message stringgetCode()- Returns the exception code (integer)
Example:
<?php
try {
throw new Exception("Error message", 500);
} catch (Exception $e) {
echo $e->getMessage(); // Error message
echo $e->getCode(); // 500
}
Multiple Catch Blocks
Handle different exception types with separate catch blocks. Catch blocks are evaluated in order, and the first matching type is executed.
Syntax:
<?php
try {
// Code
} catch (SpecificException $e) {
// Handle specific exception
} catch (Exception $e) {
// Handle general exception
}
Example:
<?php
class ValidationException extends Exception {}
class DatabaseException extends Exception {}
function process($data) {
if (empty($data)) {
throw new ValidationException("Data is empty");
}
throw new DatabaseException("Connection failed");
}
try {
process("");
} catch (ValidationException $e) {
echo "Validation error: " . $e->getMessage();
} catch (DatabaseException $e) {
echo "Database error: " . $e->getMessage();
} catch (Exception $e) {
echo "Unknown error: " . $e->getMessage();
}
// Output: Validation error: Data is empty
Multi-Catch (PHP 7.1)
Catch multiple exception types in a single block using the pipe operator (|). This is useful when the same handling logic applies to different exception types.
Syntax:
<?php
catch (TypeA | TypeB | TypeC $e) {
// Handle any of these types
}
Example:
<?php
class NetworkException extends Exception {}
class TimeoutException extends Exception {}
try {
throw new NetworkException("Connection lost");
} catch (NetworkException | TimeoutException $e) {
echo "Communication error: " . $e->getMessage();
}
// Output: Communication error: Connection lost
Try/Catch/Finally
The finally block always executes, regardless of whether an exception was thrown or caught. Use it for cleanup operations.
Syntax:
<?php
try {
// Code that may throw
} catch (Exception $e) {
// Handle exception
} finally {
// Always executes
}
Example:
<?php
try {
echo "try\n";
throw new Exception("error");
} catch (Exception $e) {
echo "catch\n";
} finally {
echo "finally\n";
}
// Output:
// try
// catch
// finally
Finally Without Catch
A finally block can exist without a catch block. The exception will propagate after the finally block executes.
Example:
<?php
function cleanup() {
try {
echo "Opening resource\n";
throw new Exception("Failed");
} finally {
echo "Cleanup\n";
}
}
try {
cleanup();
} catch (Exception $e) {
echo "Caught: " . $e->getMessage();
}
// Output:
// Opening resource
// Cleanup
// Caught: Failed
Throw as Expression (PHP 8.0)
In PHP 8.0+, throw can be used as an expression in contexts that previously only allowed values, such as arrow functions, null coalescing operators, and ternary expressions.
With Null Coalescing:
<?php
function getValue($value) {
return $value ?? throw new Exception("Value required");
}
try {
$result = getValue(null);
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
// Output: Error: Value required
With Ternary:
<?php
$age = -5;
$result = $age >= 0
? "Age is $age"
: throw new Exception("Invalid age");
In Arrow Functions:
<?php
$validate = fn($x) => $x > 0 ? $x : throw new Exception("Must be positive");
try {
echo $validate(-1);
} catch (Exception $e) {
echo $e->getMessage(); // Must be positive
}
Exception Inheritance
Custom exception classes can extend Exception or other exception classes, enabling hierarchical exception handling.
Example:
<?php
class CustomException extends Exception {}
class ChildException extends CustomException {}
// Catching parent catches children
try {
throw new ChildException("child error");
} catch (CustomException $e) {
echo "Caught via parent: " . $e->getMessage();
}
// Output: Caught via parent: child error
Nested Try/Catch
Try/catch blocks can be nested to handle exceptions at different levels.
Example:
<?php
try {
echo "outer try\n";
try {
echo "inner try\n";
throw new Exception("inner exception");
} catch (Exception $e) {
echo "inner catch: " . $e->getMessage() . "\n";
throw new Exception("re-thrown");
}
} catch (Exception $e) {
echo "outer catch: " . $e->getMessage();
}
// Output:
// outer try
// inner try
// inner catch: inner exception
// outer catch: re-thrown
Uncaught Exceptions
If an exception is not caught, VHP will terminate execution and display an error message.
Example:
<?php
throw new Exception("This will crash");
// Fatal error: Uncaught Exception: This will crash
Best Practices
- Specific Before General: Order catch blocks from most specific to most general exception types
- Use Finally for Cleanup: Place resource cleanup (file handles, connections) in finally blocks
- Informative Messages: Provide clear, actionable error messages
- Appropriate Exception Types: Create custom exception classes for different error categories
- Don’t Catch Everything: Only catch exceptions you can handle appropriately
- Preserve Context: When re-throwing, consider preserving the original exception context
Notes
- Case-insensitive: Exception class names follow PHP’s case-insensitive class naming
- Exception Hierarchy: All exceptions must extend the base
Exceptionclass - Finally Always Runs: The finally block executes even if there’s a return statement in try or catch
- Multi-catch Order: In multi-catch, types are checked left to right
- Expression Context: Throw expressions have higher precedence than most operators
Type Declarations (PHP 7.0+)
VHP supports PHP’s type declaration syntax for parameters and return types with full runtime validation. Type mismatches throw descriptive TypeErrors.
Parameter Type Hints
Type hints are validated at runtime to ensure type safety.
<?php
function greet(string $name, int $age): void {
echo "Hello, $name. You are $age years old.";
}
greet("Alice", 30); // Output: Hello, Alice. You are 30 years old.
greet(123, "thirty"); // TypeError: Expected string for parameter $name, got int
Supported simple types:
int- Integer valuesfloat- Floating-point valuesstring- String valuesbool- Boolean values (true/false)array- Array valuesobject- Object instancescallable- Callable values (functions, closures)iterable- Arrays or Traversable objectsmixed- Any type (PHP 8.0+)
Nullable Types (PHP 7.1)
Prefix a type with ? to allow null values.
<?php
function setName(?string $name): void {
if ($name === null) {
echo "Name not provided";
} else {
echo "Name: $name";
}
}
setName(null); // Output: Name not provided
setName("Bob"); // Output: Name: Bob
setName(123); // TypeError: Expected ?string, got int
Union Types (PHP 8.0)
Use | to accept multiple types.
<?php
function process(int|string $value): string {
if (is_int($value)) {
return "Number: " . $value;
}
return "Text: " . $value;
}
echo process(42); // Output: Number: 42
echo process("hello"); // Output: Text: hello
echo process(true); // TypeError: Expected int|string, got bool
Union types can include null:
<?php
function getValue(): int|float|null {
return null;
}
Intersection Types (PHP 8.1)
Use & to require multiple types (typically interfaces).
<?php
function process(Iterator&Countable $collection): int {
return count($collection);
}
DNF Types (PHP 8.2)
DNF (Disjunctive Normal Form) types allow complex type declarations combining union (|) and intersection (&) types. The format is (A&B)|C meaning “either an object that implements both A and B, or just C”.
Syntax:
<?php
// Basic DNF: (A&B)|C
function process((Loggable&Serializable)|Cacheable $obj): void {
// Accept objects that implement both Loggable AND Serializable,
// OR objects that implement Cacheable
}
// Multiple intersections: (A&B)|(C&D)
function handle((Iterator&Countable)|(ArrayAccess&Traversable) $collection): void {
// Complex type combinations
}
// DNF with simple type: (A&B)|C|D
function accept((A&B)|C|D $value): void {
// Mixed intersection and simple types
}
Example:
<?php
interface Loggable {
public function log();
}
interface Serializable {
public function serialize();
}
interface Cacheable {
public function cache();
}
// Accept either (Loggable AND Serializable) OR Cacheable
function process((Loggable&Serializable)|Cacheable $obj): void {
if ($obj instanceof Loggable) {
$obj->log();
}
if ($obj instanceof Cacheable) {
$obj->cache();
}
}
class LogSerializable implements Loggable, Serializable {
public function log() { echo "Logged\n"; }
public function serialize() { return "serialized"; }
}
class Cache implements Cacheable {
public function cache() { echo "Cached\n"; }
}
process(new LogSerializable()); // OK: matches (Loggable&Serializable)
process(new Cache()); // OK: matches Cacheable
With Return Types:
<?php
interface I1 {}
interface I2 {}
interface I3 {}
class A implements I1, I2 {}
function get(): (I1&I2)|I3 {
return new A(); // Returns object matching (I1&I2)
}
$result = get();
echo $result instanceof I1 ? "yes" : "no"; // yes
Multiple Intersection Groups:
<?php
interface A {}
interface B {}
interface C {}
interface D {}
class AB implements A, B {}
class CD implements C, D {}
function test((A&B)|(C&D) $obj): string {
return get_class($obj);
}
echo test(new AB()); // AB
echo test(new CD()); // CD
Notes:
- DNF Form Required: Must be in Disjunctive Normal Form:
(A&B)|Cis valid, butA&(B|C)is not - Parentheses Required: Intersections in unions must use parentheses:
(A&B)|C - Type Mixing: Can mix single types with intersections:
(A&B)|C|D - Validation: At runtime, the value must match at least one “branch” of the union
- Intersection Matching: To match an intersection branch, must implement ALL types in that intersection
- Class/Interface Types Only: Only class/interface types can be in intersections (not int, string, etc.)
- Inheritance Support: Subclasses that implement required interfaces match the type
Error Handling:
If a value doesn’t match any branch of the DNF type, a TypeError is thrown:
<?php
interface A {}
interface B {}
interface C {}
class OnlyA implements A {}
function process((A&B)|C $obj): void {
echo "OK";
}
// TypeError: Expected type (A&B)|C, got OnlyA
// OnlyA only implements A, not both A and B, and not C
process(new OnlyA());
Return Type Declarations
Specify the type a function returns using : type after the parameter list.
<?php
function add(int $a, int $b): int {
return $a + $b;
}
function getUser(): array {
return ["name" => "Alice", "age" => 30];
}
Special return types:
void- Function returns nothing (PHP 7.1)never- Function never returns (throws or exits) (PHP 8.1)static- Returns instance of called class (PHP 8.0)self- Returns instance of declaring classparent- Returns instance of parent class
<?php
function log(string $message): void {
echo $message;
// No return statement needed
}
function fail(): never {
throw new Exception("Always fails");
}
Class Type Hints
Use class names as type hints:
<?php
class User {
public $name;
}
function processUser(User $user): string {
return $user->name;
}
Method Type Hints
Type hints work the same way in class methods:
<?php
class Calculator {
public function add(int $a, int $b): int {
return $a + $b;
}
public function divide(float $a, float $b): ?float {
if ($b === 0.0) {
return null;
}
return $a / $b;
}
}
Runtime Type Validation
Type validation is fully implemented:
- ✅ Type declarations are parsed and stored in the AST
- ✅ All PHP 7.0-8.1 type syntax is supported
- ✅ Types are validated at runtime for parameters and return values
- ✅ Descriptive TypeErrors are thrown for mismatches
Example:
<?php
function add(int $a, int $b): int {
return $a + $b;
}
echo add(5, 10); // Output: 15
echo add("hello", "world"); // TypeError: Expected int, got string
Supported validations:
- Simple types: int, string, float, bool, array, object, callable, iterable, mixed
- Nullable types: ?int, ?string, etc.
-
Union types: int string, int float null - Class types: User, Exception, etc.
- Return types: void, never, static
Best Practices
- Use type hints for runtime safety and better code documentation
- Prefer specific types over
mixedwhen possible - Use nullable types (
?int) instead of union with null when accepting a single type or null - Handle type errors gracefully with try/catch when needed
- Test edge cases to ensure your type hints match actual usage
Declare Directive - Strict Types (PHP 7.0)
The declare directive controls code execution aspects. The most important use is declare(strict_types=1) which enables strict type checking for function calls.
What is Strict Types Mode?
By default, PHP uses coercive type checking - it attempts to convert values to match type hints:
"42"(string) is automatically converted to42(int)1.9(float) is automatically converted to1(int)
With declare(strict_types=1), PHP uses strict type checking - type mismatches are rejected:
- Passing a string where int is expected throws a TypeError
- Passing a float where int is expected throws a TypeError (except: int can widen to float)
Basic Syntax
<?php
declare(strict_types=1);
function add(int $a, int $b): int {
return $a + $b;
}
echo add(1, 2); // OK: 3
echo add(1.5, 2); // TypeError: must be of type int, float given
echo add("1", 2); // TypeError: must be of type int, string given
Without Strict Types (Default Coercive Mode)
<?php
function add(int $a, int $b): int {
return $a + $b;
}
echo add("1", "2"); // OK: 3 (strings coerced to integers)
echo add(1.9, 2.1); // OK: 3 (floats coerced to integers: 1 + 2)
File-Scope Declaration
declare(strict_types=1) must be the first statement in a file (except for an opening PHP tag):
<?php
declare(strict_types=1); // Must be first!
function test(int $x): void {
echo $x;
}
test(123); // OK
test("123"); // TypeError
Invalid positions:
<?php
echo "hello";
declare(strict_types=1); // Error: must be first statement
Block-Scoped Declaration
You can also use declare with a block to apply strict types to specific code:
<?php
function outer(int $n) {
return $n;
}
declare(strict_types=1) {
function inner(int $n) {
return $n;
}
// inner() uses strict types
}
// outer() uses coercive types
echo outer("42"); // OK: 42 (coerced)
Alternative syntax:
<?php
declare(strict_types=1):
// Code here
enddeclare;
Type Widening Exception
Even in strict mode, int can be passed where float is expected (widening is allowed):
<?php
declare(strict_types=1);
function half(float $n): float {
return $n / 2;
}
echo half(10); // OK: 5 (int widened to float)
echo half(10.0); // OK: 5
echo half("10"); // TypeError: string cannot be passed as float
Caller vs Callee
Important: The strict_types mode is determined by the calling file, not the function definition file.
If file A has declare(strict_types=1) and calls a function in file B (without strict_types), the call uses strict checking because it’s happening in file A.
Multiple Directives
You can declare multiple directives at once:
<?php
declare(strict_types=1, encoding='UTF-8');
Supported directives:
strict_types- Enable/disable strict type checking (0 or 1)encoding- Character encoding (mostly ignored in modern PHP)ticks- Forregister_tick_function()(advanced, rarely used)
Validation Rules
Strict mode type matching:
| Hint Type | Value Type | Allowed? |
|---|---|---|
int |
int |
✅ Yes |
int |
float |
❌ No |
int |
string |
❌ No |
float |
int |
✅ Yes (widening) |
float |
float |
✅ Yes |
float |
string |
❌ No |
string |
string |
✅ Yes |
string |
int |
❌ No |
bool |
bool |
✅ Yes |
bool |
int |
❌ No |
array |
array |
✅ Yes |
mixed |
any | ✅ Yes |
Use Cases
When to use strict_types:
- ✅ New projects where type safety is a priority
- ✅ Library code that needs predictable behavior
- ✅ Code that works with sensitive data (financial, security)
- ✅ When you want to catch type bugs early
When not to use strict_types:
- ⚠️ Legacy codebases that rely on type coercion
- ⚠️ Code that processes user input (strings) as numbers
- ⚠️ Quick scripts where flexibility is more important than strictness
Examples
Strict validation for parameters:
<?php
declare(strict_types=1);
function processUser(string $name, int $age, bool $active): void {
echo "$name is $age years old";
echo $active ? " (active)" : " (inactive)";
}
processUser("Alice", 30, true); // OK
processUser("Bob", "25", true); // TypeError: age must be int
processUser("Carol", 40, 1); // TypeError: active must be bool
Strict validation for return types:
<?php
declare(strict_types=1);
function getCount(): int {
return 42; // OK
}
function getBadCount(): int {
return "42"; // TypeError: return value must be int, string given
}
Mixed strict and coercive:
<?php
// File: strict.php
declare(strict_types=1);
require 'coercive.php';
// This call uses strict checking (caller's mode)
coerciveFunction(10); // OK
coerciveFunction("10"); // TypeError
// File: coercive.php
function coerciveFunction(int $x) {
echo $x;
}
Key Points
declare(strict_types=1)affects parameter and return type validation- It must be the first statement in the file
- It’s determined by the calling file, not the function definition
- int → float widening is allowed even in strict mode
- No narrowing is allowed (float → int, string → int, etc.)
- Applies to user-defined functions and built-in functions with type hints
- Cannot be changed at runtime (compile-time directive)
Date and Time Functions
VHP provides core PHP date/time functions for Unix timestamp operations.
time()
<?php
$timestamp = time();
echo $timestamp; // Current Unix timestamp
mktime()
<?php
$ts = mktime(12, 30, 45, 6, 15, 2024);
echo gmdate('Y-m-d H:i:s', $ts); // 2024-06-15 12:30:45
strtotime()
<?php
// ISO 8601 format
$ts = strtotime('2024-06-15');
// Relative time
$ts2 = strtotime('+1 day', mktime(0, 0, 0, 1, 1, 2024));
// Special keywords
$ts3 = strtotime('now');
gmdate()
<?php
$ts = mktime(12, 30, 45, 6, 15, 2024);
echo gmdate('Y-m-d H:i:s', $ts); // 2024-06-15 12:30:45
echo gmdate('l, F j, Y', $ts); // Saturday, June 15, 2024
gmstrftime()
<?php
$ts = mktime(12, 30, 45, 6, 15, 2024);
echo gmstrftime('%Y-%m-%d %H:%M:%S', $ts); // 2024-06-15 12:30:45
echo gmstrftime('%A, %B %d, %Y', $ts); // Saturday, June 15, 2024
Supported strtotime() Formats:
- Special keywords: “now”, “today”, “tomorrow”, “yesterday”
- ISO 8601: “2024-06-15”, “2024-06-15T14:30:00”
- Relative: “+1 day”, “-2 weeks”, “+3 months”, “+1 hour”
- Basic dates: “15 Jan 2024”
Supported gmdate() Format Specifiers:
- Year: Y (4-digit), y (2-digit)
- Month: m (02), n (1-12)
- Day: d (02), j (1-31)
- Time: H (24-hour), i (minutes), s (seconds)
- Weekday: l (full), D (abbr)
- Month name: F (full), M (abbr)
VHP