PHP Escopo
Você cria $taxa = 0.1; no começo do arquivo, escreve uma função que usa $taxa… e o PHP diz que a variável não existe. Não é defeito: é o escopo, a regra que diz onde cada variável pode ser vista. Entender essa regra evita um dos avisos mais comuns de quem começa e explica por que funções são tão seguras de usar.
- O que é o escopo global (o arquivo) e o escopo local (cada função).
- Por que a função não enxerga a variável de fora (
Warning: Undefined variable) e vice-versa. - O jeito certo de levar dados para dentro e para fora: parâmetros e
return. globale$GLOBALS: como funcionam e por que evitar.static: uma variável de função que se lembra do valor entre as chamadas.- Uma surpresa do PHP:
if,foreforeachnão criam escopo.
Você precisa saber criar funções com parâmetros e return. Os exemplos rodam na máquina da Ana, na pasta ~/site.
A ideia antes do código: a cozinha do restaurante
Pense na lojinha de doces da Ana. No salão ficam os clientes, o caixa, o cardápio. A cozinha é uma sala fechada: o confeiteiro lá dentro não vê o que está no salão. Se ele precisa de alguma coisa, alguém tem que entregar pela janelinha (o pedido). Quando o doce fica pronto, ele devolve pela janelinha. O que acontece na cozinha (as tigelas, a farinha espalhada) fica na cozinha.
| Na lojinha | No PHP |
|---|---|
| O salão | O escopo global: o arquivo, fora de qualquer função. |
| A cozinha (uma sala fechada) | O escopo local de uma função. Cada função tem a sua. |
| O pedido entregue pela janelinha | Os parâmetros. |
| O doce devolvido pela janelinha | O return. |
| As tigelas sujas | As variáveis criadas dentro da função: somem quando ela termina. |
A função não vê as variáveis de fora
<?php
$x = 10;
function mostrar()
{
echo "Dentro da função: x = $x\n";
}
mostrar();
echo "Fora da função: x = $x\n";
$ php fora.php Warning: Undefined variable $x in /home/ana/site/fora.php on line 6 Dentro da função: x = Fora da função: x = 10
- Warning
- "Aviso": o PHP não parou o programa, mas algo está errado (veja Lendo Erros do PHP).
- Undefined variable $x
- "Variável indefinida $x". Dentro da função, não existe nenhum
$x. - on line 6
- A linha do
echodentro da função. A linha 2, onde o$xfoi criado, está fora. - x =
- No lugar da variável que não existe, o PHP usa
null, que no texto vira nada.
A última linha mostra que o $x de fora continua lá, valendo 10. Ele só não é visível dentro da função. Em PHP, uma função começa sempre com a "sala vazia": as únicas variáveis que existem lá dentro são os parâmetros e as que ela mesma criar.
Em JavaScript e Python, uma função enxerga as variáveis de fora automaticamente. Em PHP, não. Se você vem dessas linguagens, este é o aviso que mais vai aparecer no começo.
E o contrário: o que nasce na função fica na função
<?php
function calcular()
{
$resultado = 2 + 3;
echo "Dentro: $resultado\n";
}
calcular();
echo "Fora: $resultado\n";
$ php dentro.php Dentro: 5 Warning: Undefined variable $resultado in /home/ana/site/dentro.php on line 9 Fora:
$resultado é uma variável local: nasce quando a função começa e é apagada quando ela termina (as tigelas sujas da cozinha). Na linha 9, fora da função, ela não existe mais.
Mesmo nome, variáveis diferentes
Como cada escopo é separado, uma variável de dentro pode ter o mesmo nome de uma de fora sem nenhum conflito. São duas caixas diferentes, com a mesma etiqueta, em salas diferentes:
<?php
$nome = "Ana";
function trocarNome()
{
$nome = "Bruno";
echo "Dentro: $nome\n";
}
trocarNome();
echo "Fora: $nome\n";
$ php mesmonome.php Dentro: Bruno Fora: Ana « o $nome de fora nunca foi tocado
Essa é a grande vantagem do escopo: você pode usar uma função sem medo de ela bagunçar as suas variáveis. Ela pode ter um $i, um $total, um $nome lá dentro, e os seus continuam intactos.
Cada chamada começa do zero
As variáveis locais são criadas de novo a cada chamada. Por isso este contador não conta:
<?php
function contarVisita()
{
$visitas = 0;
$visitas++;
echo "Visitas: $visitas\n";
}
contarVisita();
contarVisita();
contarVisita();
$ php recomeca.php Visitas: 1 Visitas: 1 Visitas: 1
Cada chamada cria um $visitas novo valendo 0, soma 1, mostra e joga fora. Guarde este exemplo: ele vai ser consertado com static, mais abaixo.
O jeito certo: parâmetros para dentro, return para fora
Se a função precisa de um valor de fora, entregue pela janelinha: um parâmetro. Se o resultado precisa sair, devolva pela janelinha: return.
<?php
$preco = 50;
$taxa = 0.1;
function comTaxa(float $preco, float $taxa): float
{
return $preco + $preco * $taxa;
}
$total = comTaxa($preco, $taxa);
echo "Total: $total\n";
echo "Outro: ", comTaxa(200, 0.05), "\n";
| Trecho | O que faz |
|---|---|
function comTaxa(float $preco, float $taxa) | Os parâmetros são variáveis locais que já nascem com os valores entregues na chamada. |
comTaxa($preco, $taxa) | Entrega os valores das variáveis de fora. Os nomes iguais são coincidência: poderiam ser outros. |
return … | Devolve o resultado. |
$total = … | Quem chamou decide onde guardar o resultado. |
$ php certo.php Total: 55 Outro: 210
Repare na última linha: como a função não depende de nenhuma variável de fora, ela funciona com quaisquer valores. É isso que torna uma função reaproveitável.
O parâmetro é uma cópia
Quando você passa uma variável para uma função, a função recebe uma cópia do valor. Mudar o parâmetro lá dentro não muda a variável de fora:
<?php
function dobrar($numero)
{
$numero = $numero * 2;
echo "Dentro: $numero\n";
}
$valor = 5;
dobrar($valor);
echo "Fora: $valor\n";
$ php parametro.php Dentro: 10 Fora: 5
Para mudar o valor de fora, o caminho normal é devolver e guardar: $valor = dobrar($valor); (com return na função). Existe também a passagem por referência (function dobrar(&$numero)), em que a função mexe na própria variável; ela aparece em Parâmetros e no foreach, mas use com cuidado, pelo mesmo motivo do global.
Missão: a função tenta usar $loja, que está do lado de fora. Rode, veja o aviso e corrija sem usar global: faça a função receber o nome da loja por parâmetro e passe $loja na chamada. A saída deve ser Bem-vinda à Doces da Ana!.
<?php
$loja = "Doces da Ana";
function saudacao(): string
{
return "Bem-vinda à $loja!";
}
echo saudacao(), "\n";
<?php
$loja = "Doces da Ana";
function saudacao(string $loja): string
{
return "Bem-vinda à $loja!";
}
echo saudacao($loja), "\n";
Antes da correção, o PHP real mostra:
$ php saudacao.php Warning: Undefined variable $loja in /home/ana/site/saudacao.php on line 6 Bem-vinda à !
global: abrir uma porta da cozinha para o salão
O PHP tem um jeito de a função usar uma variável do escopo global: a palavra global ("global"), seguida do nome da variável, dentro da função.
<?php
$x = 10;
function mostrar()
{
global $x;
echo "Dentro da função: x = $x\n";
$x = 99;
}
mostrar();
echo "Fora da função: x = $x\n";
- global $x;
- "Dentro desta função,
$xé a variável$xlá de fora." A partir dessa linha, a função lê e escreve na variável global. - $x = 99;
- Muda a variável de fora, de verdade.
$ php global.php Dentro da função: x = 10 Fora da função: x = 99 « a função mudou a variável de fora
Existe ainda o array $GLOBALS, que está disponível em qualquer lugar e guarda todas as variáveis globais pelo nome (é uma das superglobais):
<?php
$loja = "Doces da Ana";
function titulo()
{
return "Bem-vindo à " . $GLOBALS["loja"];
}
echo titulo(), "\n";
$ php globals.php Bem-vindo à Doces da Ana
Por que evitar o global
Funciona, mas tira da função a maior qualidade dela: ser uma sala fechada. Veja um bug típico. Duas funções de um carrinho de compras usam o mesmo $total global, e uma delas reaproveita o nome sem pensar:
<?php
$total = 0;
function adicionarAoCarrinho($preco)
{
global $total;
$total += $preco;
}
function calcularFrete()
{
global $total;
$total = $total > 100 ? 0 : 15; // ops: reaproveitou o nome $total
return $total;
}
adicionarAoCarrinho(40);
adicionarAoCarrinho(30);
echo "Frete: R$ ", calcularFrete(), "\n";
echo "Total das compras: R$ $total\n";
$ php globalbug.php Frete: R$ 15 Total das compras: R$ 15 « deveria ser 70!
Nenhum aviso: o código é válido. Mas calcularFrete sobrescreveu o total das compras. Num programa grande, descobrir qual das funções mexeu na variável global pode levar horas. Os problemas do global:
- Dependência escondida: olhando só a primeira linha da função (
function calcularFrete()), você não sabe que ela precisa de$totalnem que vai mudá-lo. - Qualquer função pode estragar o valor, como no exemplo.
- Difícil de reaproveitar e de testar: a função só funciona se existir uma variável com aquele nome exato do lado de fora.
A versão sem global deixa tudo à vista: function calcularFrete(float $total): float, chamada com calcularFrete($total). Para valores fixos que todo o programa usa (o nome da loja, a taxa de entrega), prefira constantes, que são visíveis em todo lugar e não podem ser alteradas por engano.
static: uma variável local que não esquece
Lembra do contador de visitas que sempre mostrava 1? A palavra static ("estática") muda a regra para uma variável: ela continua local (ninguém de fora a vê), mas não é apagada quando a função termina. Na próxima chamada, ela ainda tem o valor anterior.
<?php
function contarVisita()
{
static $visitas = 0;
$visitas++;
echo "Visitas: $visitas\n";
}
contarVisita();
contarVisita();
contarVisita();
- static
- "Esta variável sobrevive entre as chamadas desta função."
- $visitas = 0
- O valor inicial. Atenção: essa atribuição acontece só na primeira chamada. Nas seguintes, a linha é pulada e a variável mantém o valor que tinha.
$ php static.php Visitas: 1 Visitas: 2 Visitas: 3
Um uso prático: gerar números em sequência, como os pedidos da lojinha. E repare que, mesmo sendo static, a variável continua invisível do lado de fora:
<?php
function proximoPedido(): string
{
static $numero = 1000;
$numero++;
return "Pedido nº $numero";
}
echo proximoPedido(), "\n";
echo proximoPedido(), "\n";
echo proximoPedido(), "\n";
echo $numero, "\n";
$ php proximoid.php Pedido nº 1001 Pedido nº 1002 Pedido nº 1003 Warning: Undefined variable $numero in /home/ana/site/proximoid.php on line 12
Até o PHP 8.2, o valor inicial de uma static tinha de ser fixo (um número, um texto, uma constante). Desde o 8.3, pode ser qualquer expressão, como static $inicio = date("H:i");, calculada uma única vez, na primeira chamada.
A static não sobrevive entre páginas
"Uma variável que não esquece" serve para um contador de visitas do site? Não. Ela só lembra enquanto o programa está rodando. Na web, cada vez que alguém abre a página, o PHP roda o script do zero e, no fim, esquece tudo. A Ana testou com o servidor embutido:
<?php
function contarVisita(): int
{
static $visitas = 0;
$visitas++;
return $visitas;
}
contarVisita();
echo "Você é o visitante nº ", contarVisita(), "\n";
$ php -S localhost:8000 > /tmp/servidor.log 2>&1 & « o servidor, em segundo plano [1] 477837 $ curl -s localhost:8000/visitas.php Você é o visitante nº 2 « duas chamadas na mesma página: a static funcionou $ curl -s localhost:8000/visitas.php Você é o visitante nº 2 « página aberta de novo: tudo recomeçou $ kill %1 « desliga o servidor
Para guardar algo entre uma página e outra, você precisa de um lugar que dure mais que o script: um arquivo (Ler e Escrever Arquivos), a sessão do visitante (Sessões) ou um banco de dados.
Uma surpresa: if, for e foreach não criam escopo
Em PHP, só as funções (e as classes, mais adiante) criam uma sala separada. As chaves de um if ou de um laço não: uma variável criada lá dentro continua existindo depois.
<?php
$nota = 8;
if ($nota >= 7) {
$situacao = "aprovada";
}
echo "Situação: $situacao\n";
foreach (["pão", "leite", "café"] as $item) {
$ultimo = $item;
}
echo "Depois do laço: \$item = $item, \$ultimo = $ultimo\n";
for ($i = 0; $i < 3; $i++) {
}
echo "Depois do for: \$i = $i\n";
$ php blocos.php Situação: aprovada Depois do laço: $item = café, $ultimo = café Depois do for: $i = 3
O $item do foreach continua valendo o último elemento, e o $i do for ficou em 3 (o valor que fez a condição $i < 3 falhar). Isso é útil às vezes, mas também é a origem de um bug famoso do foreach por referência, explicado em foreach. E cuidado com o if: se a nota fosse 5, $situacao nunca seria criada, e a linha do echo daria Undefined variable $situacao. Crie a variável antes do if com um valor padrão.
Resumo dos escopos do PHP
| Onde a variável foi criada | Onde ela é visível | Quanto tempo dura |
|---|---|---|
| Fora de funções (global) | Fora de funções; dentro, só com global ou $GLOBALS | Até o fim do script |
| Dentro de uma função (local), inclusive parâmetros | Só dentro daquela chamada da função | Até a função terminar |
Dentro de uma função, com static | Só dentro da função | Até o fim do script (sobrevive entre chamadas) |
Dentro de if, for, foreach, while | No escopo em volta (o bloco não isola nada) | Como o escopo em volta |
Superglobais: $_GET, $_POST, $GLOBALS… | Em todo lugar | Até o fim do script |
Constantes (const, define) | Em todo lugar | Até o fim do script |
As funções anônimas têm a sua própria forma de trazer variáveis de fora, o use; veja Funções Anônimas e Arrow Functions. E arquivos carregados com include compartilham o escopo de onde foram incluídos (include e require).
Quando dá errado
Warning: Undefined variable $x dentro de uma função
$ php fora.php Warning: Undefined variable $x in /home/ana/site/fora.php on line 6 Dentro da função: x = Fora da função: x = 10
O que aconteceu: a função usou uma variável que só existe fora dela.
Como consertar: receba o valor por parâmetro (function mostrar($x), chamada com mostrar($x)). Se for um valor fixo do programa inteiro, transforme em constante. global é o último recurso.
Warning: Undefined variable fora da função
$ php dentro.php Dentro: 5 Warning: Undefined variable $resultado in /home/ana/site/dentro.php on line 9 Fora:
O que aconteceu: você tentou usar, fora, uma variável criada dentro da função. Ela foi apagada quando a função terminou.
Como consertar: a função deve return $resultado;, e quem chama guarda: $resultado = calcular();.
$ php recomeca.php Visitas: 1 Visitas: 1 Visitas: 1
O que aconteceu: variável local comum é recriada a cada chamada.
Como consertar: static $visitas = 0; (se o valor só precisa durar enquanto o script roda), ou guarde o valor fora da função e passe/devolva.
global $x = 5;: unexpected token "="
<?php
function f()
{
global $x = 5;
}
$ php globalatrib.php Parse error: syntax error, unexpected token "=", expecting "," or ";" in /home/ana/site/globalatrib.php on line 4
O que significa: depois de global $x o PHP espera , (para mais variáveis: global $x, $y;) ou ;. O global só "abre a porta", não atribui.
Como consertar: duas linhas: global $x; e depois $x = 5;. (Com static, ao contrário, o valor inicial vai na mesma linha: static $x = 5;.)
$ php globalbug.php Frete: R$ 15 Total das compras: R$ 15
O que aconteceu: alguma função com global $total alterou a variável.
Como consertar: procure todos os global $total do projeto (é por isso que é difícil). A solução duradoura é eliminar o global: parâmetros para dentro, return para fora.
Referência
| Forma | O que faz |
|---|---|
| variável dentro da função | Local: só existe naquela chamada. |
| parâmetro | Local, já criado com uma cópia do valor passado. |
return $valor; | Leva um valor para fora da função. |
global $a, $b; | Dentro da função, $a e $b passam a ser as variáveis globais (lê e escreve). Evite. |
$GLOBALS["nome"] | Acessa a variável global $nome de qualquer lugar. Evite. |
static $n = 0; | Variável local que mantém o valor entre as chamadas; o = 0 roda só na primeira. Expressões no valor inicial: PHP 8.3+. |
use ($x) | Em funções anônimas, leva uma cópia de $x (veja Closures). |
Exercícios
1. Lembrar entre as chamadas
Complete para o contador continuar de onde parou a cada chamada:
2. Levar o valor para fora
Complete a última linha da função para o resultado sair dela:
3. O jeito antigo (e desaconselhado)
Complete para a função usar a variável global $loja:
4. O que mostra? $a = 1; function f() { $a = 2; } f(); echo $a;
5. O que mostra? foreach ([1, 2, 3] as $n) { } echo $n;
6. Qual é a melhor forma de uma função usar a taxa de entrega que está numa variável fora dela?
Missão final
Missão: a padaria da esquina distribui senhas, mas a função sempre devolve Senha 1. Mude uma linha para as três chamadas mostrarem Senha 1, Senha 2 e Senha 3.
<?php
function proximaSenha(): string
{
$senha = 0;
$senha++;
return "Senha $senha";
}
echo proximaSenha(), "\n";
echo proximaSenha(), "\n";
echo proximaSenha(), "\n";
<?php
function proximaSenha(): string
{
static $senha = 0;
$senha++;
return "Senha $senha";
}
echo proximaSenha(), "\n";
echo proximaSenha(), "\n";
echo proximaSenha(), "\n";
Resumo
- Cada função é uma "sala fechada": não vê as variáveis de fora, e as de dentro somem quando ela termina. Usar uma variável do outro lado gera
Warning: Undefined variable. - Variáveis com o mesmo nome dentro e fora são variáveis diferentes.
- O jeito certo: dados entram por parâmetros (cópias) e saem por
return. globale$GLOBALSdão acesso às variáveis de fora, mas escondem dependências e permitem que qualquer função estrague o valor. Evite; para valores fixos, use constantes.staticcria uma variável local que lembra o valor entre as chamadas, só enquanto o script roda (na web, cada página recomeça).if,for,foreachewhilenão criam escopo: o que nasce dentro continua existindo depois.