Ricerca…


Che cos'è la giocoleria di tipo?

PHP è un linguaggio vagamente dattiloscritto. Ciò significa che, per impostazione predefinita, non richiede che gli operandi di un'espressione siano dello stesso tipo (o compatibili). Ad esempio, puoi aggiungere un numero a una stringa e aspettarti che funzioni.

var_dump ("This is example number " . 1);

L'output sarà:

string (24) "Questo è l'esempio numero 1"

PHP realizza ciò automaticamente rilasciando tipi di variabili incompatibili in tipi che consentono l'esecuzione dell'operazione richiesta. Nel caso precedente, eseguirà il cast del letterale 1 intero in una stringa, il che significa che può essere concatenato alla stringa precedente. Questo è indicato come tipo giocoleria. Questa è una funzionalità molto potente di PHP, ma è anche una funzione che può portare a un sacco di strappi se non ne sei consapevole e può anche portare a problemi di sicurezza.

Considera quanto segue:

if (1 == $variable) {
    // do something
}

L'intento sembra essere che il programmatore stia controllando che una variabile abbia un valore di 1. Ma cosa succede se la variabile $ ha invece un valore di "1 e mezzo"? La risposta potrebbe sorprenderti.

$variable = "1 and a half";
var_dump (1 == $variable);

Il risultato è:

bool (true)

Perché è successo? È perché PHP si è reso conto che la stringa "1 e mezzo" non è un numero intero, ma deve essere per confrontarla con l'intero 1. Invece di fallire, PHP avvia la giocoleria e tenta di convertire la variabile in un numero intero. Lo fa prendendo tutti i caratteri all'inizio della stringa che possono essere convertiti in numeri interi e nel cast. Si ferma non appena incontra un personaggio che non può essere trattato come un numero. Quindi "1 e mezzo" viene lanciato su intero 1.

Certo, questo è un esempio molto elaborato, ma serve a dimostrare il problema. I prossimi esempi riguarderanno alcuni casi in cui mi sono imbattuto in errori causati dalla giocoleria di tipo avvenuta nel software reale.

Lettura da un file

Durante la lettura da un file, vogliamo essere in grado di sapere quando abbiamo raggiunto la fine di quel file. Sapendo che fgets() restituisce false alla fine del file, potremmo usarlo come condizione per un ciclo. Tuttavia, se i dati restituiti dall'ultima lettura risultano essere qualcosa che viene valutato come booleano false , è possibile che il nostro ciclo di lettura file si interrompa prematuramente.

$handle = fopen ("/path/to/my/file", "r");

if ($handle === false) {
    throw new Exception ("Failed to open file for reading");
}

while ($data = fgets($handle)) {
    echo ("Current file line is $data\n");
}

fclose ($handle);

Se il file da leggere contiene una riga vuota, il ciclo while verrà terminato in quel punto, poiché la stringa vuota viene valutata come booleana false .

Invece, possiamo verificare esplicitamente il valore false booleano, usando operatori di uguaglianza rigorosa :

while (($data = fgets($handle)) !== false) {
    echo ("Current file line is $data\n");
}

Nota questo è un esempio forzato; nella vita reale useremmo il seguente ciclo:

while (!feof($handle)) {
    $data = fgets($handle);
    echo ("Current file line is $data\n");
}

O sostituire il tutto con:

$filedata = file("/path/to/my/file");
foreach ($filedata as $data) {
    echo ("Current file line is $data\n");
}

Passa sorprese

Le istruzioni switch utilizzano un confronto non rigoroso per determinare le corrispondenze. Questo può portare a brutte sorprese . Ad esempio, prendere in considerazione la seguente dichiarazione:

switch ($name) {
    case 'input 1':
        $mode = 'output_1';
        break;
    case 'input 2':
        $mode = 'output_2';
        break;
    default:
        $mode = 'unknown';
        break;
}

Questa è un'istruzione molto semplice e funziona come previsto quando $name è una stringa, ma può causare problemi in altro modo. Ad esempio, se $name è intero 0 , allora il tipo-juggling avverrà durante il confronto. Tuttavia, è il valore letterale nella dichiarazione del caso a destreggiarsi, non la condizione nell'istruzione switch. La stringa "input 1" viene convertita in numero intero 0 che corrisponde al valore di input del numero intero 0 . Il risultato è se fornisci un valore di intero 0 , il primo caso viene sempre eseguito.

Ci sono alcune soluzioni a questo problema:

Casting esplicito

Il valore può essere tipizzato in una stringa prima del confronto:

switch ((string)$name) {
...
}

Oppure è possibile utilizzare anche una funzione nota per la restituzione di una stringa:

switch (strval($name)) {
...
}

Entrambi questi metodi assicurano che il valore sia dello stesso tipo del valore nelle istruzioni case .

Evitare l' switch

L'utilizzo di una dichiarazione if ci fornirà il controllo su come viene eseguito il confronto, consentendoci di utilizzare rigorosi operatori di confronto :

if ($name === "input 1") {
    $mode = "output_1";
} elseif ($name === "input 2") {
    $mode = "output_2";
} else {
    $mode = "unknown";
}

Tipizzazione rigorosa

Dal PHP 7.0, alcuni degli effetti dannosi della giocoleria di tipo possono essere mitigati con una tipizzazione rigorosa . Includendo questa declare dichiarativa come prima riga del file, PHP imporrà le dichiarazioni del tipo di parametro e restituirà dichiarazioni di tipo lanciando un'eccezione TypeError .

declare(strict_types=1);

Ad esempio, questo codice, utilizzando le definizioni del tipo di parametro, genererà un'eccezione Catchable di tipo TypeError durante l'esecuzione:

<?php
declare(strict_types=1);

function sum(int $a, int $b) {
    return $a + $b;
}

echo sum("1", 2);

Allo stesso modo, questo codice utilizza una dichiarazione di tipo restituito; genererà inoltre un'eccezione se tenta di restituire qualcosa di diverso da un numero intero:

<?php
declare(strict_types=1);

function returner($a): int {
    return $a;
}

returner("this is a string");


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow