Referência: o que é escopo de variável, quais variáveis ​​são acessíveis a partir de onde e quais são os erros de "variável indefinida"?

167

Nota: Esta é uma pergunta de referência para lidar com o escopo variável no PHP. Feche qualquer uma das muitas perguntas que se encaixam nesse padrão como uma duplicata deste.

O que é "escopo variável" em PHP? As variáveis ​​de um arquivo .php estão acessíveis em outro? Por que às vezes recebo erros de "variável indefinida" ?

deceze
fonte
1
Se você intitulado isso como "Undefined variable" você vai ter cargas mais hits :) bom trabalho embora
Dale
@Dale, na verdade não. 2k vistas em 2 anos é ....
Pacerier
7
@Pacerier ... sobre a hora certa de deixar um comentário aleatório?
Dale
@ Pacerier Eu realmente não tenho certeza do que você está tentando dizer com esse comentário. "É ...." ... o que ?! : P
deceze
@Dale, agora é a hora certa: uau, mesmo que a pergunta tenha estagnado por 2 anos, após a palavra " apátrida " ser adicionada ao seu GoogleDex, sua taxa de acertos literalmente 3x-ed em apenas 6 meses.
Pacerier 02/09/2015

Respostas:

188

O que é "escopo variável"?

As variáveis ​​têm um "escopo" limitado ou "locais de onde são acessíveis". Só porque você escreveu $foo = 'bar';uma vez em algum lugar do seu aplicativo não significa que você pode se referir $foode qualquer outro lugar dentro do aplicativo. A variável $footem um determinado escopo dentro do qual é válida e apenas o código no mesmo escopo tem acesso à variável.

Como um escopo é definido no PHP?

Muito simples: o PHP tem escopo de função . Esse é o único tipo de separador de escopo que existe no PHP. Variáveis ​​dentro de uma função estão disponíveis apenas dentro dessa função. Variáveis ​​fora das funções estão disponíveis em qualquer lugar fora das funções, mas não dentro de nenhuma função. Isso significa que há um escopo especial no PHP: o escopo global . Qualquer variável declarada fora de qualquer função está dentro desse escopo global.

Exemplo:

<?php

$foo = 'bar';

function myFunc() {
    $baz = 42;
}

$fooestá no escopo global , $bazestá dentro de um escopo localmyFunc . Somente o código interno myFunctem acesso $baz. Somente o código externo myFunc tem acesso $foo. Nenhum dos dois tem acesso ao outro:

<?php

$foo = 'bar';

function myFunc() {
    $baz = 42;

    echo $foo;  // doesn't work
    echo $baz;  // works
}

echo $foo;  // works
echo $baz;  // doesn't work

Escopo e arquivos incluídos

Os limites do arquivo não separam o escopo:

a.php

<?php

$foo = 'bar';

b.php

<?php

include 'a.php';

echo $foo;  // works!

As mesmas regras se aplicam ao includecódigo d e a qualquer outro código: somente functions escopo separado. Para fins de escopo, você pode incluir arquivos como código de copiar e colar:

c.php

<?php

function myFunc() {
    include 'a.php';

    echo $foo;  // works
}

myFunc();

echo $foo;  // doesn't work!

No exemplo acima, a.phpfoi incluída dentro myFunc, quaisquer variáveis ​​dentro a.phpsomente têm escopo de função local. Só porque eles parecem estar no escopo global a.phpnão significa necessariamente que estão, na verdade depende do contexto em que o código é incluído / executado.

E as funções dentro de funções e classes?

Toda nova functiondeclaração introduz um novo escopo, é simples assim.

funções (anônimas) dentro de funções

function foo() {
    $foo = 'bar';

    $bar = function () {
        // no access to $foo
        $baz = 'baz';
    };

    // no access to $baz
}

Aulas

$foo = 'foo';

class Bar {

    public function baz() {
        // no access to $foo
        $baz = 'baz';
    }

}

// no access to $baz

Para que serve o escopo?

Lidar com problemas de escopo pode parecer irritante, mas o escopo variável limitado é essencial para escrever aplicativos complexos! Se todas as variáveis ​​declaradas estivessem disponíveis em qualquer outro lugar dentro do aplicativo, você passaria por todas as variáveis ​​sem uma maneira real de rastrear o que muda o quê. Existem apenas tantos nomes sensíveis que você pode atribuir às suas variáveis. Você provavelmente deseja usar a variável " $name" em mais de um local. Se você pudesse ter esse nome de variável exclusivo apenas uma vez no aplicativo, seria necessário recorrer a esquemas de nomeação realmente complicados para garantir que suas variáveis ​​sejam únicas e que você não esteja alterando a variável errada pelo código errado.

Observar:

function foo() {
    echo $bar;
}

Se não houvesse escopo, o que a função acima faria? De onde $barvem? Que estado tem? É mesmo inicializado? Você tem que verificar todas as vezes? Isso não é sustentável. O que nos leva a ...

Cruzando limites do escopo

O caminho certo: passando variáveis ​​dentro e fora

function foo($bar) {
    echo $bar;
    return 42;
}

A variável $barestá entrando explicitamente nesse escopo como argumento de função. Apenas olhando para esta função, fica claro de onde os valores com os quais trabalha se originam. Em seguida, ele retorna explicitamente um valor. O chamador tem confiança para saber com quais variáveis ​​a função trabalhará e de onde vêm seus valores de retorno:

$baz   = 'baz';
$blarg = foo($baz);

Estendendo o escopo das variáveis ​​para funções anônimas

$foo = 'bar';

$baz = function () use ($foo) {
    echo $foo;
};

$baz();

A função anônima inclui explicitamente $foo do seu escopo circundante. Observe que isso não é o mesmo que escopo global .

O caminho errado: global

Como dito anteriormente, o escopo global é algo especial e as funções podem importar explicitamente variáveis ​​dele:

$foo = 'bar';

function baz() {
    global $foo;
    echo $foo;
    $foo = 'baz';
}

Esta função usa e modifica a variável global $foo. Não faça isso! (A menos que você realmente saiba realmente o que está fazendo e, mesmo assim: não saiba!)

Todo o chamador desta função vê é o seguinte:

baz(); // outputs "bar"
unset($foo);
baz(); // no output, WTF?!
baz(); // outputs "baz", WTF?!?!!

Não há indicação de que essa função tenha efeitos colaterais , mas tem. Isso facilmente se torna uma bagunça emaranhada, pois algumas funções continuam modificando e exigindo algum estado global. Você quer que as funções sejam sem estado , agindo apenas em suas entradas e retornando saídas definidas, por mais que você as chame.

Você deve evitar usar o escopo global da maneira mais possível; certamente você não deve estar "puxando" variáveis ​​do escopo global para um escopo local.

deceze
fonte
Você acabou de dizer o caminho erradoglobal , por favor, diga-nos quando devemos usar global? E por favor explique (um pouco) o que é static..?
@stack Não existe um caminho "certo" para global. Está sempre errado. A passagem dos parâmetros da função está correta. staticé explicado bem no manual e não tem muito a ver com o escopo. Em poucas palavras, pode ser pensado como uma "variável global com escopo definido". Estou expandindo um pouco seu uso aqui kunststube.net/static .
deceze
Meu pensamento simples é que se uma variável php é importante o suficiente para merecer um status global, ela merece uma coluna em um banco de dados. Talvez seja um exagero, mas é uma abordagem à prova de idiota que se encaixa a minha sagacidade programação medíocre
Arthur Tarasov
@ Arthur Há muito o que descompactar lá… ಠ_ಠ Essa certamente não é uma abordagem que eu aprovaria.
Deceze
@ deceze um pouquinho de argumento acontecendo aqui hoje stackoverflow.com/q/51409392 - onde o OP menciona que a duplicata (aqui) não menciona include_oncee possivelmente require_oncetambém deve ser adicionada em algum lugar; apenas dizendo. O OP também votou na reabertura da pergunta. O post seria um caso especial e o que deveria ser feito?
Funk Quarenta Niner
10

Embora as variáveis ​​definidas dentro do escopo de uma função não possam ser acessadas de fora, isso não significa que você não poderá usar seus valores depois que a função for concluída. O PHP tem uma staticpalavra - chave conhecida que é amplamente usada no PHP orientado a objetos para definir métodos e propriedades estáticas, mas é preciso ter em mente que statictambém pode ser usada dentro de funções para definir variáveis ​​estáticas.

O que é 'variável estática'?

A variável estática difere da variável comum definida no escopo da função, caso não perca valor quando a execução do programa deixa esse escopo. Vamos considerar o seguinte exemplo de uso de variáveis ​​estáticas:

function countSheep($num) {
 static $counter = 0;
 $counter += $num;
 echo "$counter sheep jumped over fence";
}

countSheep(1);
countSheep(2);
countSheep(3);

Resultado:

1 sheep jumped over fence
3 sheep jumped over fence
6 sheep jumped over fence

Se tivéssemos definido $countersem static, cada vez que o valor ecoado seria o mesmo que o $numparâmetro passado para a função. O uso staticpermite criar esse contador simples sem solução adicional.

Casos de uso de variáveis ​​estáticas

  1. Para armazenar valores entre as chamadas de função consequentes.
  2. Armazenar valores entre chamadas recursivas quando não houver maneira (ou nenhuma finalidade) de transmiti-las como parâmetros.
  3. Para armazenar em cache o valor que normalmente é melhor recuperar uma vez. Por exemplo, resultado da leitura de arquivo imutável no servidor.

Truques

A variável estática existe apenas em um escopo de função local. Ele não pode ser acessado fora da função em que foi definido. Portanto, você pode ter certeza de que ele manterá seu valor inalterado até a próxima chamada para essa função.

A variável estática pode ser definida apenas como uma expressão escalar ou escalar (desde o PHP 5.6). Atribuir outros valores a ele inevitavelmente leva a uma falha pelo menos no momento em que este artigo foi escrito. No entanto, você é capaz de fazer isso apenas na próxima linha do seu código:

function countSheep($num) {
  static $counter = 0;
  $counter += sqrt($num);//imagine we need to take root of our sheep each time
  echo "$counter sheep jumped over fence";
}

Resultado:

2 sheep jumped over fence
5 sheep jumped over fence
9 sheep jumped over fence

A função estática é meio 'compartilhada' entre métodos de objetos da mesma classe. É fácil entender, visualizando o seguinte exemplo:

class SomeClass {
  public function foo() {
    static $x = 0;
    echo ++$x;
  }
}

$object1 = new SomeClass;
$object2 = new SomeClass;

$object1->foo(); // 1
$object2->foo(); // 2 oops, $object2 uses the same static $x as $object1
$object1->foo(); // 3 now $object1 increments $x
$object2->foo(); // 4 and now his twin brother

Isso funciona apenas com objetos da mesma classe. Se os objetos forem de classes diferentes (até mesmo se estendendo), o comportamento dos vars estáticos será o esperado.

A variável estática é a única maneira de manter valores entre as chamadas para uma função?

Outra maneira de manter valores entre as chamadas de função é usar fechamentos. Os encerramentos foram introduzidos no PHP 5.3. Em duas palavras, eles permitem limitar o acesso a algum conjunto de variáveis ​​dentro de um escopo de função para outra função anônima que será a única maneira de acessá-las. Estar em variáveis ​​de fechamento pode imitar (com mais ou menos sucesso) conceitos de POO como 'constantes de classe' (se elas foram passadas em fechamento por valor) ou 'propriedades privadas' (se passadas por referência) em programação estruturada.

O último realmente permite usar closures em vez de variáveis ​​estáticas. O que usar sempre depende do desenvolvedor decidir, mas deve-se mencionar que as variáveis ​​estáticas são definitivamente úteis ao trabalhar com recursões e merecem ser notadas pelos desenvolvedores.

Alex Myznikov
fonte
2

Não postarei uma resposta completa para a pergunta, pois as existentes e o manual do PHP fazem um ótimo trabalho para explicar a maior parte disso.

Mas um assunto que foi perdido foi o de superglobals , incluindo o comumente usado $_POST, $_GET, $_SESSION, etc. Essas variáveis são matrizes que estão sempre disponíveis, em qualquer âmbito, sem uma globaldeclaração.

Por exemplo, esta função imprimirá o nome do usuário executando o script PHP. A variável está disponível para a função sem nenhum problema.

<?php
function test() {
    echo $_ENV["user"];
}

A regra geral de "os globais são ruins" é normalmente alterada no PHP para "os globais são ruins, mas os superglobais estão bem", desde que não se use mal. (Todas essas variáveis ​​são graváveis, portanto, elas podem ser usadas para evitar a injeção de dependência, se você for realmente péssimo.)

Não há garantia de que essas variáveis ​​estejam presentes; um administrador pode desativar alguns ou todos eles usando a variables_orderdirectiva em php.ini, mas isso não é um comportamento comum.


Uma lista dos superglobais atuais:

  • $GLOBALS - Todas as variáveis ​​globais no script atual
  • $_SERVER - Informações sobre o servidor e o ambiente de execução
  • $_GET - Valores passados ​​na cadeia de consulta da URL, independentemente do método HTTP usado para a solicitação
  • $_POST- Valores passados ​​em uma solicitação HTTP POST com application/x-www-form-urlencodedou multipart/form-datatipos MIME
  • $_FILES - Arquivos passados ​​em uma solicitação HTTP POST com um multipart/form-data tipo MIME
  • $_COOKIE - Cookies passados ​​com a solicitação atual
  • $_SESSION - Variáveis ​​de sessão armazenadas internamente pelo PHP
  • $_REQUEST- Normalmente, uma combinação de $_GETe $_POST, mas às vezes $_COOKIES. O conteúdo é determinado pela request_orderdiretiva emphp.ini .
  • $_ENV - As variáveis ​​de ambiente do script atual
miken32
fonte