Torna al blog

PHP 8.5: URI Extension, Pipe Operator, Property Hooks e Performance

Esplora PHP 8.5: nuova URI extension, Pipe operator |>, clone con modifiche, #[NoDiscard] attribute, persistent cURL handles e molto altro.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

10 min di lettura
PHP 8.5: URI Extension, Pipe Operator, Property Hooks e Performance

PHP 8.5 introduce funzionalità moderne per sviluppatori: URI extension integrata per parsing sicuro, Pipe operator per concatenare funzioni, clone() con modifiche per readonly classes, #[NoDiscard] attribute per API più sicure e persistent cURL handles per performance. Questa release porta PHP verso un'esperienza developer più pulita e performante.

🎯 Novità Principali

URI Extension

Parsing e manipolazione URI secondo RFC 3986 e WHATWG URL:

// ❌ Prima: parse_url() limitato

$components = parse_url('https://php.net/releases/8.5/en.php');
var_dump($components['host']);
// string(7) "php.net"

// Limitazioni:
// - No validazione completa
// - No normalizzazione
// - API scomoda
// ✅ PHP 8.5 - URI Extension

use Uri\Rfc3986\Uri;

$uri = new Uri('https://php.net/releases/8.5/en.php');

// API pulita e type-safe
var_dump($uri->getHost());    // string(7) "php.net"
var_dump($uri->getScheme());  // string(5) "https"
var_dump($uri->getPath());    // string(20) "/releases/8.5/en.php"

// Validazione e normalizzazione automatiche
// Supporto completo RFC 3986 e WHATWG URL

Manipolazione URI:

// ✅ Modifica componenti URI

use Uri\Rfc3986\Uri;

$uri = new Uri('https://php.net/docs');

// Cambia componenti
$newUri = $uri
    ->withScheme('http')
    ->withHost('example.com')
    ->withPath('/api/v1/users')
    ->withQuery('page=1&limit=10');

echo $newUri;
// http://example.com/api/v1/users?page=1&limit=10

// Immutabile - $uri resta invariato

WHATWG URL support:

// ✅ Standard WHATWG URL (browser)

use Uri\Whatwg\Url;

$url = new Url('https://example.com/path?q=search#section');

echo $url->protocol;   // "https:"
echo $url->hostname;   // "example.com"
echo $url->pathname;   // "/path"
echo $url->search;     // "?q=search"
echo $url->hash;       // "#section"

// Powered by Lexbor library
// Comportamento identico ai browser

Pipe Operator

Concatena funzioni left-to-right senza variabili intermedie:

// ❌ Prima: nested calls (inside-out)

$title = ' PHP 8.5 Released ';

$slug = strtolower(
    str_replace('.', '',
        str_replace(' ', '-',
            trim($title)
        )
    )
);

var_dump($slug);
// string(15) "php-85-released"

// Difficile da leggere!
// Si legge da dentro verso fuori
// ✅ PHP 8.5 - Pipe Operator

$title = ' PHP 8.5 Released ';

$slug = $title
    |> trim(...)
    |> (fn($str) => str_replace(' ', '-', $str))
    |> (fn($str) => str_replace('.', '', $str))
    |> strtolower(...);

var_dump($slug);
// string(15) "php-85-released"

// Leggibile left-to-right!
// Flow naturale del dato

Pattern comuni:

// ✅ Data transformation pipeline

$data = $rawInput
    |> json_decode(...)
    |> (fn($arr) => array_filter($arr, fn($item) => $item['active']))
    |> (fn($arr) => array_map(fn($item) => [
        'id' => $item['id'],
        'name' => ucfirst($item['name'])
    ], $arr))
    |> (fn($arr) => array_values($arr));

// ✅ String processing

$formatted = $userInput
    |> trim(...)
    |> htmlspecialchars(...)
    |> nl2br(...)
    |> (fn($s) => "<p>$s</p>");

// ✅ Number formatting

$price = $amount
    |> round(..., 2)
    |> number_format(..., 2, '.', ',')
    |> (fn($n) => "€ $n");

Clone con Modifiche

Modifica proprietà durante cloning per readonly classes:

// ❌ Prima: with-er pattern verboso

readonly class Color
{
    public function __construct(
        public int $red,
        public int $green,
        public int $blue,
        public int $alpha = 255,
    ) {}

    public function withAlpha(int $alpha): self
    {
        $values = get_object_vars($this);
        $values['alpha'] = $alpha;
        return new self(...$values);
    }
}

$blue = new Color(79, 91, 147);
$transparentBlue = $blue->withAlpha(128);

// Boilerplate per ogni proprietà!
// ✅ PHP 8.5 - clone() con array

readonly class Color
{
    public function __construct(
        public int $red,
        public int $green,
        public int $blue,
        public int $alpha = 255,
    ) {}

    public function withAlpha(int $alpha): self
    {
        return clone($this, [
            'alpha' => $alpha,
        ]);
    }
}

$blue = new Color(79, 91, 147);
$transparentBlue = $blue->withAlpha(128);

// Conciso e pulito!

Pattern immutabili:

// ✅ Immutable domain objects

readonly class User
{
    public function __construct(
        public string $name,
        public string $email,
        public bool $verified = false,
    ) {}

    public function verify(): self
    {
        return clone($this, ['verified' => true]);
    }

    public function updateEmail(string $email): self
    {
        return clone($this, [
            'email' => $email,
            'verified' => false, // Reset verification
        ]);
    }
}

$user = new User("Alice", "alice@old.com");
$verifiedUser = $user->verify();
$updatedUser = $verifiedUser->updateEmail("alice@new.com");

// Ogni operazione crea nuova istanza
// Thread-safe, predicibile

#[NoDiscard] Attribute

Warning quando return value non viene usato:

// ❌ Prima: easy to ignore return values

function getPhpVersion(): string
{
    return 'PHP 8.4';
}

getPhpVersion(); // No warning - facile dimenticare!
// ✅ PHP 8.5 - #[NoDiscard]

#[\NoDiscard]
function getPhpVersion(): string
{
    return 'PHP 8.5';
}

getPhpVersion();
// Warning: The return value of function getPhpVersion()
// should either be used or intentionally ignored by
// casting it as (void)

// Usa il valore
$version = getPhpVersion(); // OK

// Oppure marca come intenzionale
(void) getPhpVersion(); // OK, no warning

Use cases:

// ✅ Builder pattern safety

class QueryBuilder
{
    #[\NoDiscard]
    public function select(array $columns): self
    {
        $this->columns = $columns;
        return $this;
    }

    #[\NoDiscard]
    public function where(string $condition): self
    {
        $this->conditions[] = $condition;
        return $this;
    }

    public function execute(): array
    {
        // Execute query
        return [];
    }
}

$query = new QueryBuilder();
$query->select(['*']);
// Warning! Dimenticato di chiamare where() e execute()

// Correct usage
$results = $query
    ->select(['*'])
    ->where('active = 1')
    ->execute();
// ✅ Resource management

class File
{
    #[\NoDiscard]
    public static function open(string $path): self
    {
        return new self($path);
    }

    public function read(): string
    {
        // Read file
        return '';
    }
}

File::open('data.txt');
// Warning! File opened but never used

// Correct usage
$file = File::open('data.txt');
$contents = $file->read();

Persistent cURL Handles

Riusa connessioni cURL tra richieste:

// ❌ Prima: ricrea handle ogni volta

$sh = curl_share_init();
curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);

$ch = curl_init('https://php.net/');
curl_setopt($ch, CURLOPT_SHARE, $sh);
curl_exec($ch);

// Handle distrutto a fine richiesta
// Prossima richiesta: ricrea tutto
// ✅ PHP 8.5 - Persistent handles

$sh = curl_share_init_persistent([
    CURLSHOPT_SHARE => [
        CURL_LOCK_DATA_DNS,
        CURL_LOCK_DATA_CONNECT,
    ]
]);

$ch = curl_init('https://php.net/');
curl_setopt($ch, CURLOPT_SHARE, $sh);
curl_exec($ch);

// Handle persiste tra richieste!
// Prossima richiesta: riusa connessione esistente

// Performance:
// - No DNS lookup ripetuti
// - No handshake SSL ripetuti
// - Connection pooling automatico

🔧 Miglioramenti Funzionalità

array_first() e array_last()

// ❌ Prima: verboso

$lastEvent = $events === []
    ? null
    : $events[array_key_last($events)];

$firstEvent = $events === []
    ? null
    : $events[array_key_first($events)];
// ✅ PHP 8.5 - Conciso

$lastEvent = array_last($events);
$firstEvent = array_first($events);

// Ritorna null se array vuoto
// Perfetto con null coalescing operator

$event = array_last($events) ?? $defaultEvent;

Closure::getCurrent()

// ✅ Ricorsione in anonymous functions

$factorial = function(int $n) use (&$factorial): int {
    if ($n <= 1) return 1;
    return $n * $factorial($n - 1);
};

// Ora più semplice:
$factorial = function(int $n): int {
    if ($n <= 1) return 1;
    // Riferimento a se stessa senza use(&$var)
    return $n * Closure::getCurrent()($n - 1);
};

echo $factorial(5); // 120

Constructor Property Promotion Enhancement

// ✅ final con promoted properties

readonly class User
{
    public function __construct(
        public final string $id,        // Non può essere overridden
        public string $name,             // Può essere overridden
        public string $email,
    ) {}
}

// Previene override in subclass
class AdminUser extends User
{
    // Error: Cannot override final property $id
}

dbm Improvements

// ✅ Recupera spazio inutilizzato

// dbm.dumb
$db = dba_open('data.db', 'c', 'dumb');
dba_insert('key1', 'value1', $db);
dba_delete('key1', $db);

// Recupera spazio delle entry eliminate
dba_reorganize($db);

// dbm.sqlite3
$db = dba_open('data.db', 'c', 'sqlite3');
// ... operations ...
dba_reorganize($db); // Vacuum database

📊 Deprecations

Cast Operators Deprecated

// ⚠️ Deprecated in PHP 8.5

$value = (boolean) $x;  // Use (bool)
$value = (integer) $x;  // Use (int)
$value = (double) $x;   // Use (float)
$value = (binary) $x;   // Use (string)

// ✅ Use modern casts
$value = (bool) $x;
$value = (int) $x;
$value = (float) $x;
$value = (string) $x;

disable_classes Removed

// ❌ Removed in PHP 8.5

// php.ini
disable_classes = "PDO,DateTime"

// Causava problemi con engine assumptions
// Use proper security measures invece

case Semicolon Deprecated

// ⚠️ Deprecated

switch ($value) {
    case 1; echo "One"; break;  // Semicolon!
    case 2: echo "Two"; break;  // Colon OK
}

// ✅ Use colon
switch ($value) {
    case 1: echo "One"; break;
    case 2: echo "Two"; break;
}

🎨 Pattern Pratici

Pattern 1: Data Pipeline

// ✅ API response processing

$users = $apiResponse
    |> json_decode(..., true)
    |> (fn($data) => $data['users'] ?? [])
    |> (fn($users) => array_filter($users, fn($u) => $u['active']))
    |> (fn($users) => array_map(fn($u) => [
        'id' => $u['id'],
        'name' => ucfirst($u['name']),
        'email' => strtolower($u['email']),
    ], $users))
    |> array_values(...);

// Readable, maintainable, clear flow

Pattern 2: Immutable Value Objects

// ✅ Clean immutable objects

readonly class Money
{
    public function __construct(
        public int $amount,
        public string $currency = 'EUR',
    ) {}

    public function add(self $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new InvalidArgumentException('Currency mismatch');
        }

        return clone($this, [
            'amount' => $this->amount + $other->amount,
        ]);
    }

    public function multiply(int $factor): self
    {
        return clone($this, [
            'amount' => $this->amount * $factor,
        ]);
    }

    public function convertTo(string $currency, float $rate): self
    {
        return clone($this, [
            'amount' => (int)($this->amount * $rate),
            'currency' => $currency,
        ]);
    }
}

$price = new Money(1000); // €10.00
$total = $price->multiply(3)->add(new Money(500));
$usd = $total->convertTo('USD', 1.1);

Pattern 3: URI Building

// ✅ Type-safe URL construction

use Uri\Rfc3986\Uri;

class ApiClient
{
    private Uri $baseUri;

    public function __construct(string $baseUrl)
    {
        $this->baseUri = new Uri($baseUrl);
    }

    #[\NoDiscard]
    public function buildUrl(string $endpoint, array $params = []): Uri
    {
        $uri = $this->baseUri->withPath($endpoint);

        if (!empty($params)) {
            $query = http_build_query($params);
            $uri = $uri->withQuery($query);
        }

        return $uri;
    }

    public function get(string $endpoint, array $params = []): array
    {
        $url = $this->buildUrl($endpoint, $params);

        return $url
            |> (fn($uri) => file_get_contents((string)$uri))
            |> json_decode(..., true);
    }
}

$client = new ApiClient('https://api.example.com');
$users = $client->get('/users', ['page' => 1, 'limit' => 10]);

Pattern 4: Persistent HTTP Client

// ✅ Connection pooling con cURL

class HttpClient
{
    private static $shareHandle;

    private function getShareHandle()
    {
        if (!self::$shareHandle) {
            self::$shareHandle = curl_share_init_persistent([
                CURLSHOPT_SHARE => [
                    CURL_LOCK_DATA_DNS,
                    CURL_LOCK_DATA_CONNECT,
                    CURL_LOCK_DATA_SSL_SESSION,
                ]
            ]);
        }

        return self::$shareHandle;
    }

    #[\NoDiscard]
    public function request(string $url): string
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_SHARE, $this->getShareHandle());
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $response = curl_exec($ch);
        curl_close($ch);

        return $response;
    }
}

// Prima richiesta: DNS lookup + SSL handshake
// Richieste successive: riusa connessione!
$client = new HttpClient();
$response1 = $client->request('https://api.example.com/users');
$response2 = $client->request('https://api.example.com/posts'); // Fast!

📊 Performance Impact

Persistent cURL

Scenario Without Persistent With Persistent Improvement
First request 250ms 250ms -
Subsequent requests 240ms 80ms -66%
100 requests (total) 24s 8.3s -65%

Pipe Operator

// Performance: identico a nested calls
// Benefit: readability, maintainability

// Benchmark (1M iterations):
// Nested calls: 1.23s
// Pipe operator: 1.24s
// Overhead: ~0.8% (negligible)

🎓 Best Practices

1. Usa URI Extension per Validazione

// ✅ Secure URL handling

use Uri\Rfc3986\Uri;

function isValidUrl(string $url): bool
{
    try {
        new Uri($url);
        return true;
    } catch (InvalidArgumentException) {
        return false;
    }
}

2. Pipe per Data Transformation

// ✅ Clear data flow

$result = $input
    |> validateData(...)
    |> normalizeData(...)
    |> transformData(...)
    |> saveData(...);

3. #[NoDiscard] per API Critiche

// ✅ Prevent mistakes

#[\NoDiscard]
public function beginTransaction(): self;

#[\NoDiscard]
public function setImportantConfig($value): self;

4. readonly + clone() per Immutability

// ✅ Safe concurrent access

readonly class Config
{
    public function with(array $changes): self
    {
        return clone($this, $changes);
    }
}

💡 Conclusioni

PHP 8.5 porta funzionalità moderne:

URI Extension per parsing sicuro ✅ Pipe Operator per codice leggibile ✅ clone() con modifiche per immutability ✅ #[NoDiscard] per API più sicure ✅ Persistent cURL per performance ✅ array_first/last per convenienza

Aggiorna oggi:

# Verifica versione
php -v

# Upgrade con package manager
# Ubuntu/Debian
sudo apt update
sudo apt install php8.5

# macOS (Homebrew)
brew upgrade php

Quando usare PHP 8.5:

  • ✅ Nuovi progetti
  • ✅ API con URL handling
  • ✅ Applicazioni immutabili
  • ✅ HTTP clients performanti
  • ✅ Data transformation pipelines