Skip to content

Latest commit

 

History

History
470 lines (351 loc) · 12.8 KB

File metadata and controls

470 lines (351 loc) · 12.8 KB

PHP 8.4 and 8.5 Language Features Support

Phan v6 provides full support for all PHP 8.4 and PHP 8.5 language features with complete type checking and error detection.

Table of Contents

  1. PHP 8.4 Features
  2. PHP 8.5 Features
  3. New Functions and Methods
  4. Type Checking Examples

PHP 8.4 Features

Property Hooks

New feature: Methods to intercept property access with custom get/set logic.

Basic Property Hook

class User {
    public string $name {
        set {
            $this->name = trim($value);
        }
    }
}

Phan validates:

  • Hook parameter types match property type
  • Hook return type matches expected value
  • Readonly properties with set hooks (PhanReadonlyPropertyHasSetHook)

Type-Safe Hooks

class Product {
    public int $price {
        set {
            if ($value < 0) {
                throw new ValueError("Price cannot be negative");
            }
            $this->price = $value;  // Type-checked
        }
        get {
            return $this->price;    // Return type verified
        }
    }
}

Detected errors:

  • PhanPropertyHookIncompatibleParamType - Parameter type mismatch
  • PhanPropertyHookIncompatibleReturnType - Return type mismatch
  • PhanPropertyHookWithDefaultValue - Invalid default values
  • PhanPropertyHookFinalOverride - Illegal override of final hook

Property Hook Examples

class Temperature {
    private float $celsius;

    public float $fahrenheit {
        get {
            return ($this->celsius * 9/5) + 32;
        }
        set {
            $this->celsius = ($value - 32) * 5/9;
        }
    }

    public function __construct(float $celsius) {
        $this->celsius = $celsius;
    }
}

$temp = new Temperature(20);
echo $temp->fahrenheit;  // 68
$temp->fahrenheit = 86;
echo $temp->celsius;     // ~30

#[Deprecated] Attribute

New feature: Mark deprecated functions, methods, and constants.

#[Deprecated(message: "Use newMethod() instead", since: "2.0.0")]
public function oldMethod(): void {
    $this->newMethod();
}

#[Deprecated]
class LegacyClass {
    #[Deprecated(message: "Use NEW_VALUE instead")]
    public const OLD_VALUE = 1;
}

Detection:

  • PhanDeprecatedFunction - Using deprecated functions
  • PhanDeprecatedMethod - Using deprecated methods
  • PhanDeprecatedClassConstant - Using deprecated constants
// Example detection
oldMethod();  // Warning: PhanDeprecatedFunction
LegacyClass::OLD_VALUE;  // Warning: PhanDeprecatedClassConstant

Typed Class Constants

New feature: Declare types for class constants with inheritance checking.

interface DatabaseConfig {
    public const int PORT = 5432;
    public const string HOST = 'localhost';
}

class PostgresConfig implements DatabaseConfig {
    public const int PORT = 5432;          // OK
    public const string HOST = '127.0.0.1'; // OK
}

class MySQLConfig implements DatabaseConfig {
    public const string PORT = '3306';     // Error: type mismatch!
    public const string HOST = 'localhost'; // OK
}

Detected errors:

  • PhanTypeMismatchDeclaredConstant - Value doesn't match declared type
  • PhanConstantTypeMismatchInheritance - Type doesn't match parent constant
  • PhanTypeMismatchDeclaredConstantNever - Value contradicts never type

Type narrowing in conditions:

if (MyEnum::STATUS === 'ACTIVE') {
    // Phan narrows MyEnum::STATUS to literal 'ACTIVE' here
}

New Functions and Methods

Phan understands all PHP 8.4 new functions with proper type signatures:

// array_find - find first element matching condition
$found = array_find([1, 2, 3, 4], fn($v) => $v > 2);  // int|null

// array_find_key - find first key matching condition
$key = array_find_key(['a' => 1, 'b' => 2], fn($v) => $v > 1);  // string|int|null

// array_any - check if any element matches
$hasEven = array_any([1, 2, 3], fn($v) => $v % 2 === 0);  // true

// array_all - check if all elements match
$allPositive = array_all([1, 2, 3], fn($v) => $v > 0);  // true

// String trimming functions with locale support
$trimmed = mb_trim("  hello  ");     // "hello"
$left = mb_ltrim("  hello");         // "hello  "
$right = mb_rtrim("hello  ");        // "  hello"
$ucfirst = mb_ucfirst("hello");      // "Hello"
$lcfirst = mb_lcfirst("HELLO");      // "hELLO"

Type checking examples:

// Type-safe usage
$numbers = [1, 2, 3, 4, 5];

// Phan knows the return types
$doubled = array_map(fn($n) => $n * 2, $numbers);  // array<int|string|mixed>
$even = array_filter($numbers, fn($n) => $n % 2 === 0);  // array

$first = array_find($numbers, fn($n) => $n > 3);  // int|null
if ($first !== null) {
    echo $first;  // Phan knows it's int here
}

PHP 8.5 Features

Pipe Operator (|>)

New feature: First-class pipe syntax for function chaining.

// Traditional chaining
$result = strtoupper(trim($input));

// With pipe operator
$result = $input |> trim(...) |> strtoupper(...);

Phan provides full type inference through pipe chains:

/**
 * @param int $value
 * @return int
 */
function double($value): int {
    return $value * 2;
}

/**
 * @param int $value
 * @return string
 */
function stringify($value): string {
    return (string) $value;
}

// Type flows correctly through the pipe
$result = 5 |> double(...) |> stringify(...);
// Phan infers: $result is string

Error detection:

function expectsString(string $s): void {}

// Type error caught at pipe level
5 |> double(...) |> expectsString(...);  // Error: int not assignable to string

#[NoDiscard] Attribute

New feature: Mark return values that shouldn't be ignored.

#[NoDiscard]
function generateId(): string {
    return uniqid('id_');
}

#[NoDiscard(message: "Session was not stored")]
function createSession(): Session {
    return new Session();
}

// Errors
generateId();  // Error: PhanNoDiscardReturnValueIgnored

// OK
$id = generateId();  // Value is used
(void) generateId();  // Explicit discard with cast

Phan detections:

  • PhanNoDiscardReturnValueIgnored - Return value was ignored

(void) Cast

New feature: Explicitly suppress #[NoDiscard] warnings.

#[NoDiscard]
function getValue(): int {
    return 42;
}

// Error
getValue();  // PhanNoDiscardReturnValueIgnored

// OK
(void) getValue();  // Explicit cast suppresses warning

This is clearer than assigning to unused variables:

// Old approach (less clear)
$_ = getValue();

// New approach (explicit intent)
(void) getValue();

Updated Function Signatures

Phan v6 includes updated function signatures for all PHP 8.5 standard library changes:

// New DateTime methods
$timestamp = DateTime::createFromTimestamp(time());
$immutable = DateTimeImmutable::createFromTimestamp(time());

// DOMXPath enhancements
$xpath->registerPHPFunctionNS('ns', 'localName', callable);

New Functions and Methods

Array Functions (PHP 8.4)

// Find first matching element
array_find([1, 2, 3, 4], fn($v) => $v > 2);  // Returns first element or null

// Find first matching key
array_find_key(['a' => 1, 'b' => 2], fn($v) => $v > 1);  // Returns first key or null

// Check if any element matches predicate
array_any([1, 2, 3], fn($v) => $v > 2);  // bool

// Check if all elements match predicate
array_all([1, 2, 3], fn($v) => $v > 0);  // bool

String Functions (PHP 8.4)

// Multibyte string trimming
mb_trim("  hello  ");      // "hello"
mb_ltrim("  hello");       // "hello  "
mb_rtrim("hello  ");       // "  hello"

// Case operations
mb_ucfirst("hello");       // "Hello"
mb_lcfirst("HELLO");       // "hELLO"

Type Checking with New Functions

$items = [1, 2, 3, 4, 5];

// Phan understands return types
if (array_any($items, fn($i) => $i > 3)) {
    // We know at least one item > 3
    $first = array_find($items, fn($i) => $i > 3);
    // Phan knows $first is int (even though it could be null)
}

Type Checking Examples

Example 1: Complete Property Hook Flow

class BankAccount {
    private float $balance = 0;

    public float $availableBalance {
        get {
            return $this->balance;
        }
        set {
            if ($value < 0) {
                throw new ValueError("Balance cannot be negative");
            }
            $this->balance = $value;
        }
    }

    public function __construct(float $initialBalance) {
        if ($initialBalance < 0) {
            throw new ValueError("Initial balance cannot be negative");
        }
        $this->balance = $initialBalance;
    }
}

// Type-safe usage
$account = new BankAccount(1000);
$account->availableBalance = 500;  // Type checked
echo $account->availableBalance;    // float

$account->availableBalance = "invalid";  // Error: string not assignable to float

Example 2: Deprecated Code Detection

#[Deprecated(message: "Use TokenFactory::create() instead")]
function legacyCreateToken(): Token {
    return TokenFactory::create();
}

class Authentication {
    public function login(string $user): void {
        $token = legacyCreateToken();  // Warning: deprecated function
        $this->validateToken($token);
    }
}

Example 3: Complex Pipe Expression

function parseJson(string $json): mixed {
    return json_decode($json, true);
}

function validateData(array $data): array {
    return $data;  // type check
}

function formatOutput(array $data): string {
    return json_encode($data);
}

// Full type-safe pipe
$result = '{"key":"value"}'
    |> parseJson(...)
    |> validateData(...)
    |> formatOutput(...);

// Phan infers: $result is string

Version Requirements

To analyze PHP 8.4/8.5 code, ensure:

  1. Phan v6.0+ installed
  2. PHP 8.1+ to run Phan (target version can be different)
  3. php-ast 1.1.3+ for PHP 8.4+ syntax support
# Check requirements
phan --version
php --version
php -r "echo phpversion('ast');"

# Configure for PHP 8.5 analysis
// .phan/config.php
return [
    'target_php_version' => '8.5',
    // ... other settings
];

Further Reading