Digite variáveis ​​de conversão no PHP, qual é o motivo prático para fazer isso?

45

PHP, como a maioria de nós sabe, tem digitação fraca . Para quem não sabe, o PHP.net diz:

O PHP não requer (ou suporta) definição explícita de tipo na declaração de variável; o tipo de uma variável é determinado pelo contexto em que a variável é usada.

Ame ou odeie, o PHP relança variáveis ​​on-the-fly. Portanto, o seguinte código é válido:

$var = "10";
$value = 10 + $var;
var_dump($value); // int(20)

O PHP também permite converter explicitamente uma variável, assim:

$var = "10";
$value = 10 + $var;
$value = (string)$value;
var_dump($value); // string(2) "20"

Isso é legal ... mas, para a minha vida, não consigo conceber uma razão prática para fazer isso.

Não tenho problemas com digitação forte em linguagens que o suportam, como Java. Tudo bem, e eu entendo completamente. Além disso, estou ciente - e compreendo perfeitamente a utilidade do - dica de tipo nos parâmetros de função.

O problema que tenho com a conversão de tipos é explicado pela citação acima. Se o PHP puder trocar tipos à vontade , poderá fazê-lo mesmo depois de forçar a conversão de um tipo; e pode fazê-lo on-the-fly quando você precisar de um determinado tipo em uma operação. Isso torna válido o seguinte:

$var = "10";
$value = (int)$var;
$value = $value . ' TaDa!';
var_dump($value); // string(8) "10 TaDa!"

Então qual é o objetivo?


Veja este exemplo teórico de um mundo em que a conversão de tipos definida pelo usuário faz sentido no PHP :

  1. Você força a variável de conversão $foocomo int(int)$foo.
  2. Você tenta armazenar um valor de sequência na variável $foo.
  3. PHP lança uma exceção !! ← Isso faria sentido. De repente, existe o motivo da conversão do tipo definido pelo usuário!

O fato de o PHP mudar as coisas conforme necessário torna vago o ponto de conversão do tipo definido pelo usuário. Por exemplo, os dois exemplos de código a seguir são equivalentes:

// example 1
$foo = 0;
$foo = (string)$foo;
$foo = '# of Reasons for the programmer to type cast $foo as a string: ' . $foo;

// example 2
$foo = 0;
$foo = (int)$foo;
$foo = '# of Reasons for the programmer to type cast $foo as a string: ' . $foo;

Um ano depois de fazer essa pergunta, adivinhe quem se viu usando a tipografia em um ambiente prático? Sinceramente.

O requisito era exibir valores monetários em um site para um menu de restaurante. O design do site exigia que os zeros à direita fossem aparados, para que a exibição tivesse a seguinte aparência:

Menu Item 1 .............. $ 4
Menu Item 2 .............. $ 7.5
Menu Item 3 .............. $ 3

A melhor maneira que encontrei para fazer isso foi lançar a variável como um float:

$price = '7.50'; // a string from the database layer.
echo 'Menu Item 2 .............. $ ' . (float)$price;

O PHP apara os zeros à direita do flutuador e, em seguida, o altera novamente como uma string para concatenação.

Stephen
fonte
Isso -> $ value = $ value. 'TaDa!'; Lançaria $ value de volta à string antes de fazer a atribuição ao valor final de $ value. Não é realmente uma surpresa que, se você forçar uma conversão de tipo, obtém uma conversão de tipo. Não sabe ao certo qual é o sentido de perguntar qual é o sentido?
Chris
"# 3. O PHP lança uma exceção !! <--- Isso faria sentido." Na verdade, isso não faria sentido algum. Isso nem sequer é um problema em Java, JavaScript ou qualquer outra linguagem de sintaxe C que eu conheça. Quem em sã consciência consideraria isso um comportamento desejável? Você quer ter (string)elencos em todos os lugares ?
Nicole
@ Renesis: você me entendeu mal. O que eu quis dizer foi que uma exceção seria lançada apenas se um usuário convertesse uma variável em tipo. O comportamento normal (onde o PHP faz a conversão para você) obviamente não causaria uma exceção. Estou tentando dizer que a conversão de tipo definida pelo usuário é discutível , mas se uma exceção fosse lançada, de repente faria sentido.
Stephen
Se você está dizendo que $intval.'bar'lança uma exceção, eu ainda discordo. Isso não lança uma exceção em nenhum idioma. (Todos os idiomas que eu conheço executam uma conversão automática ou a .toString()). Se você está dizendo que $intval = $stringvallança uma exceção, está falando de uma linguagem fortemente tipada. Eu não quis parecer rude, então, desculpe se eu fiz. Eu apenas acho que vai contra o que todo desenvolvedor está acostumado e é muito, muito menos conveniente.
Nicole
@ Stephen - eu postei uma resposta depois de alguma investigação. Resultados realmente interessantes - eu pensei que dois dos casos mostrariam um propósito para a transmissão, mas o PHP é ainda mais estranho do que eu pensava.
Nicole

Respostas:

32

Em uma linguagem de tipo fraco, a conversão de tipo existe para remover a ambiguidade nas operações digitadas; caso contrário, o compilador / intérprete usaria ordem ou outras regras para supor qual operação usar.

Normalmente, eu diria que o PHP segue esse padrão, mas nos casos que verifiquei, o PHP se comportou de maneira contra-intuitiva em cada um.

Aqui estão esses casos, usando JavaScript como uma linguagem de comparação.

Concatentação de String

Obviamente, isso não é um problema no PHP porque existem operadores de concatenação de strings ( .) e de adição ( +) separados .

Javascript
var a = 5;
var b = "10"
var incorrect = a + b; // "510"
var correct = a + Number(b); // 15

Comparação de strings

Geralmente, nos sistemas de computadores "5" é maior que "10" porque não o interpreta como um número. Não é assim no PHP, que, mesmo que as duas sejam strings, percebe que são números e remove a necessidade de uma conversão):

Javascript
console.log("5" > "10" ? "true" : "false"); // true
PHP
echo "5" > "10" ? "true" : "false";  // false!

Digitação de assinatura de função

O PHP implementa uma verificação de tipo básica nas assinaturas de funções, mas infelizmente é tão falho que provavelmente provavelmente não pode ser usado.

Eu pensei que poderia estar fazendo algo errado, mas um comentário sobre os documentos confirma que tipos internos diferentes de array não podem ser usados ​​em assinaturas de funções PHP - embora a mensagem de erro seja enganosa.

PHP
function testprint(string $a) {
    echo $a;
}

$test = 5;
testprint((string)5); // "Catchable fatal error: Argument 1 passed to testprint()
                      //  must be an instance of string, string given" WTF?

E, diferente de qualquer outro idioma que eu conheço, mesmo que você use um tipo que ele entenda, null não poderá mais ser passado para esse argumento ( must be an instance of array, null given). Quão estúpido.

Interpretação booleana

[ Editar ]: Este é novo. Pensei em outro caso e, novamente, a lógica é revertida do JavaScript.

Javascript
console.log("0" ? "true" : "false"); // True, as expected. Non-empty string.
PHP
echo "0" ? "true" : "false"; // False! This one probably causes a lot of bugs.

Então, em conclusão, o único caso útil em que consigo pensar é ... (drumroll)

Tipo de truncamento

Em outras palavras, quando você tem um valor de um tipo (digamos string) e deseja interpretá-lo como outro tipo (int) e deseja forçá-lo a se tornar um dos conjuntos válidos de valores nesse tipo:

$val = "test";
$val2 = "10";
$intval = (int)$val; // 0
$intval2 = (int)$val2; // 10
$boolval = (bool)$intval // false
$boolval2 = (bool)$intval2 // true
$props = (array)$myobject // associative array of $myobject's properties

Não consigo ver qual upcasting (para um tipo que engloba mais valores) realmente ganharia você.

Então, embora eu discorde do seu uso proposto de digitação (você está propondo essencialmente a digitação estática , mas com a ambiguidade de que somente se fosse lançada à força em um tipo geraria um erro - o que causaria confusão), acho que é uma boa pergunta, porque aparentemente o casting tem muito pouco propósito em PHP.

Nicole
fonte
Ok, que tal um E_NOTICEentão? :)
Stephen
@ Stephen E_NOTICEpode estar ok, mas para mim o estado ambíguo é preocupante - como você saberia olhando um pouco de código se a variável estivesse nesse estado (tendo sido lançada em outro lugar)? Além disso, encontrei outra condição e a adicionei à minha resposta.
Nicole
1
Quanto à avaliação booleana, os documentos do PHP afirmam claramente o que é considerado falso ao avaliar como booleano e a cadeia vazia e a cadeia "0" são consideradas falsas. Portanto, mesmo quando isso parece bizarro, é um comportamento normal e esperado.
Jacek Prucia
para adicionar um pouco à confusão: echo "010" == 010 e echo "0x10" == 0x10;-)
vartec
1
Observe que, a partir do PHP 7 , as notas desta resposta sobre dicas de tipo escalar são imprecisas.
John V.
15

Você está misturando os conceitos de tipo fraco / forte e dinâmico / estático.

O PHP é fraco e dinâmico, mas seu problema é com o conceito de tipo dinâmico. Isso significa que variáveis ​​não têm um tipo, valores têm.

Uma 'conversão de tipo' é uma expressão que produz um novo valor de um tipo diferente do original; não faz nada com a variável (se houver alguma) envolvida.

A única situação em que digito regularmente os valores de conversão está nos parâmetros SQL numéricos. Você deveria higienizar / escapar qualquer valor de entrada inserido nas instruções SQL ou (muito melhor) usar consultas parametrizadas. Mas, se você quiser algum valor que DEVE ser um número inteiro, é muito mais fácil convertê-lo.

Considerar:

function get_by_id ($id) {
   $id = (int)$id;
   $q = "SELECT * FROM table WHERE id=$id LIMIT 1";
   ........
}

se eu deixasse de fora a primeira linha, $idseria um vetor fácil para injeção de SQL. O elenco garante que seja um número inteiro inofensivo; qualquer tentativa de inserir algum SQL resultaria simplesmente em uma consulta paraid=0

Javier
fonte
Eu aceito isso. Agora, quanto à utilidade da fundição de tipo?
Stephen
É engraçado você abrir a injeção de SQL. Eu estava discutindo sobre SO com alguém usando essa técnica para higienizar a entrada do usuário. Mas que problema esse método resolve que mysql_real_escape_string($id);ainda não soluciona ?
Stephen
é mais curto :-) é claro, para strings eu uso consultas parametrizadas, ou (se estiver usando a extensão antiga do mysql) a escape.
11558 Javier
2
mysql_real_escape_string()tem uma vulnerabilidade de não fazer nada com cadeias de caracteres como '0x01ABCDEF' (ou seja, representação hexadecimal de um número inteiro). Em algumas codificações multibyte (não Unicode por sorte), uma string como essa pode ser usada para interromper a consulta (porque ela é avaliada pelo MySQL como algo que contém uma citação). É por isso que mysql_real_escape_string()nem is_int()é a melhor opção para lidar com valores inteiros. Typecasting é.
Mchl
Um link com mais alguns detalhes: ilia.ws/archives/...
Mchl
4

Um uso para a conversão de tipos em PHP que eu encontrei:

estou desenvolvendo um aplicativo Android que faz solicitações HTTP para scripts PHP em um servidor para recuperar dados de um banco de dados. O script armazena dados na forma de um objeto PHP (ou matriz associativa) e é retornado como um objeto JSON para o aplicativo. Sem conversão de tipo, eu receberia algo como isto:

{ "user" : { "id" : "1", "name" : "Bob" } }

Mas, usando a conversão de tipo PHP (int)no ID do usuário ao armazená-lo no objeto PHP, recebo isso de volta ao aplicativo:

{ "user" : { "id" : 1, "name" : "Bob" } }

Então, quando o objeto JSON é analisado no aplicativo, isso evita que eu precise analisar o ID em um número inteiro!

Veja, muito útil.

Ryan
fonte
Eu não tinha pensado em formatar dados para sistemas externos de tipos fortes a consumir. 1
Stephen
Isso é especialmente verdadeiro quando se fala de JSON com sistemas externos como o Elasticsearch. A json_encode () - valor ed "5" vai dar resultados muito diferentes do que o valor 5.
Johan Fredrik Varen
3

Um exemplo é a objectos com um método __toString: $str = $obj->__toString();vs $str = (string) $obj;. Há muito menos digitação no segundo, e o material extra é a pontuação, que leva mais tempo para digitar. Eu também acho que é mais legível, embora outros possam discordar.

Outra é fazer uma matriz de elemento único: array($item);vs (array) $item;. Isso colocará qualquer tipo escalar (número inteiro, recurso etc.) dentro de uma matriz.
Por outro lado, se $itemfor um objeto, suas propriedades se tornarão chaves para seus valores. No entanto, acho que a conversão de objeto-> matriz é um pouco estranha: propriedades privadas e protegidas fazem parte da matriz e são renomeadas. Para citar a documentação do PHP : variáveis ​​privadas têm o nome da classe anexado ao nome da variável; variáveis ​​protegidas têm um '*' anexado ao nome da variável.

Outro uso é a conversão de dados GET / POST em tipos apropriados para um banco de dados. O MySQL pode lidar com isso sozinho, mas acho que os servidores mais compatíveis com ANSI podem rejeitar os dados. A razão de eu apenas mencionar bancos de dados é que, na maioria dos outros casos, os dados terão uma operação executada de acordo com seu tipo em algum momento (ou seja, int / floats geralmente terá cálculos executados neles, etc.).

Alan Pearce
fonte
Estes são ótimos exemplos de como a conversão de tipos funciona. No entanto, não estou convencido de que eles preencham uma necessidade . Sim, você pode converter um objeto em uma matriz, mas por quê? Eu acho que porque você poderia usar as inúmeras funções da matriz PHP na nova matriz, mas não consigo entender como isso seria útil. Além disso, o PHP geralmente cria consultas de strings para enviar a um banco de dados MySQL, portanto o tipo de variável é irrelevante (a conversão automática de strings a partir de intou floatocorrerá ao criar a consulta). (array) $itemé legal , mas útil?
Stephen
Eu realmente concordo. Enquanto as digitava, pensei em alguns usos, mas não pensei. Para o material do banco de dados, se os parâmetros fazem parte da string de consulta, você está certo, a transmissão não tem propósito. No entanto, ao usar consultas parametrizadas (o que é sempre uma boa ideia), é possível especificar os tipos de parâmetros.
Alan Pearce
Aha! Você pode ter encontrado um motivo válido com as consultas parametrizadas.
Stephen
0

Este script:

$tags = _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}

funcionará bem para, script.php?tags[]=onemas falhará script.php?tags=one, porque _GET['tags']retorna uma matriz no primeiro caso, mas não no segundo. Como o script foi escrito para esperar uma matriz (e você tem menos controle sobre a sequência de consultas enviada ao script), o problema pode ser resolvido lançando adequadamente o resultado de _GET:

$tags = (array) _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}
Beldaz
fonte
0

Também pode ser usado como um método rápido e sujo para garantir que dados não confiáveis ​​não quebrem algo, por exemplo, se estiver usando um serviço remoto que possui validação de porcaria e deve aceitar apenas números.

$amount = (float) $_POST['amount'];

if( $amount > 0 ){
    $remoteService->doacalculationwithanumber( $amount );    
}

Obviamente, isso é falho e também é tratado implicitamente pelo operador de comparação na instrução if, mas é útil para garantir que você saiba exatamente o que seu código está fazendo.

Gruffputs
fonte
1
Só que não quebra. Mesmo que $_POST['amount']contenha uma cadeia de lixo, o php avaliaria que não era maior que zero. Se contivesse uma sequência que representasse um número positivo, avaliaria true.
Stephen
1
Não é inteiramente verdade. Considere que $ amount está sendo passado para um serviço de terceiros dentro do condicional que deve receber um número. Se alguém passasse $ _POST ['amount'] = "100 bobinas", remover (flutuar) ainda permitiria que o condicional passasse, mas $ amount não seria um número.
Gruffputs
-2

Um "uso" das variáveis ​​de re-conversão do PHP on-the-fly que eu vejo em uso frequentemente é ao recuperar dados de fontes externas (entrada do usuário ou banco de dados). Ele permite que os codificadores (note que eu não disse desenvolvedores) ignorem (ou nem mesmo aprendam) os diferentes tipos de dados disponíveis em diferentes fontes.

Um codificador (observe que eu não disse desenvolvedor) cujo código eu herdei e ainda mantenho não parece saber que existe uma diferença entre a string "20"retornada na $_GETsuper variável e entre a operação inteira20 + 20 quando ela o adiciona ao o valor no banco de dados. Ela tem sorte que o PHP usa .para concatenação de strings e não +como qualquer outra linguagem, porque eu vi o código dela "adicionar" duas strings (uma varcahrdo MySQL e um valor da $_GET) e obter um int.

Este é um exemplo prático? Somente no sentido de permitir que os codificadores se safem sem saber com que tipos de dados estão trabalhando. Eu pessoalmente odeio isso.

dotancohen
fonte
2
Não vejo como essa resposta agrega valor à discussão. O fato de o PHP permitir que um engenheiro (ou programador, ou codificador, o que você tem) execute operações matemáticas em strings já é bastante claro na questão.
Stephen
Obrigado Stephen. Talvez eu tenha usado muitas palavras para dizer "PHP permite que pessoas que não sabem o que é um tipo de dados criem aplicativos que fazem o que esperam em condições ideais".
dotancohen