Dia 2 — Strings e operadores: PHP e MySQL

Informatica · Conteudo · publicado em 02/10/2026
Dia 2 de 17

Strings e operadores

Aula 1

Operadores aritméticos

Operadores aritméticos

O PHP resolve contas com os operadores usuais, mas três diferem do esperado em outras linguagens: a divisão / sempre devolve float, o resto % segue o sinal do dividendo e a potenciacao ** associa para a direita.

Os sete operadores

OperadorNomeExemploResultado
+soma17 + 522
-subtracao17 - 512
*multiplicação17 * 585
/divisão17 / 53.4 (sempre float)
**potenciacao2 ** 101024
%resto da divisão17 % 52
intdiv()divisão inteiraintdiv(17, 5)3 (sempre int)

intdiv($a, $b) e a alternativa quando o resultado precisa ser inteiro de verdade; ela trunca em direcao a menor, então intdiv(-7, 2) devolve -3, e não -4.

Divisão com texto

O / converte o operando textual: '10' / 2 resulta em 5.0. Se algum lado não for numerico, o PHP 8 lanca TypeError ou DivisionByZeroError em vez de devolver aviso. Checar o denominador antes de dividir evita a excecao:

$r = $b !== 0 ? intdiv($a, $b) : 'divisao por zero';

Precedencia

Da mais forte para a fraca: **, depois * / %, depois + -, e por fim a concatenacao com ponto. O parenteses sempre resolve.

2 + 3 * 4;      // 14 — o * vem antes
(2 + 3) * 4;    // 20
2 ** 3 ** 2;    // 512 — associa para a direita: 2 ** (3 ** 2)
-2 ** 2;        // -4 — o sinal fica de fora da potenciacao

O caso -2 2 e o mais enganoso: em quase toda linguagem seria 4, e no PHP e -4. Com sinal negativo em potência, escreva (-2) 2.

Arredondar para exibir

A conta e a exibicao são passos separados: guarde o float, arredonde na hora de mostrar.

round(8.456, 2);                  // 8.46 — arredonda meio para cima
floor(8.9);                       // 8
ceil(8.1);                        // 9
number_format(1234.5, 2, ',', '.'); // 1.234,50 — padrão brasileiro

O round() sem casas decimais devolve float: round(2.5) resulta em 3.0, e não em 3. Quando o valor precisa sair inteiro, converta com (int) ou passe as casas: round(2.5, 0).

Para exibir na ordem brasileira, o number_format() recebe quatro argumentos na ordem: valor, casas decimais, separador decimal e separador de milhar. A ordem contraria — decimal primeiro, milhar depois — e a causa mais comum de 1,234.50 sair ao contrario.

Exemplo

<?php
declare(strict_types=1);

$a = 17;
$b = 5;

echo "1. Os sete operadores aritmeticos\n";
echo '   soma        17 + 5        = ' . ($a + $b) . "\n";
echo '   subtracao   17 - 5        = ' . ($a - $b) . "\n";
echo '   multiplica   17 * 5       = ' . ($a * $b) . "\n";
echo '   divisao      17 / 5        = ' . ($a / $b) . "\n";
echo '   divisao int  intdiv(17,5)  = ' . intdiv($a, $b) . "\n";
echo '   resto        17 % 5        = ' . ($a % $b) . "\n";
echo '   potencia     2 ** 10       = ' . (2 ** 10) . "\n";

// A divisao com / sempre devolve float, mesmo com resultado inteiro exato.
echo "\n2. / devolve float, intdiv() devolve int\n";
var_dump($a / $b, $a / 4, intdiv($a, $b), 4 / 2);

// O % e o resto da divisao inteira e segue o sinal do dividendo.
// Ele funciona com int; com floatlanca TypeError em modo estrito.
echo "\n3. O resto % segue o sinal do dividendo\n";
echo '   17 % 5  = ' . (17 % 5) . "\n";
echo '   -17 % 5 = ' . (-17 % 5) . "\n";
echo '   17 % -5 = ' . (17 % -5) . "\n";

// Precedencia: * e / tem prioridade sobre + e -, e o parenteses resolve o resto.
echo "\n4. Precedencia e parenteses\n";
echo '   2 + 3 * 4     = ' . (2 + 3 * 4) . "\n";
echo '   (2 + 3) * 4   = ' . ((2 + 3) * 4) . "\n";
echo '   2 ** 3 ** 2   = ' . (2 ** 3 ** 2) . "\n";
echo '   -2 ** 2        = ' . (-2 ** 2) . "\n";

// Concatenacao nao e operador: e o ponto, e ele tem precedencia baixa.
echo "\n5. O ponto concatena, mas nao e divisao\n";
$total = 1 + 2 . ' reais';
echo '   1 + 2 . " reais" = ' . $total . "\n";
echo '   "1" . "2"        = ' . ('1' . '2') . "\n";
echo '   "1" + "2"        = ' . ('1' + '2') . "\n";

// Arredondar e formatar para exibicao.
echo "\n6. round, floor, ceil e number_format\n";
echo '   round(8.456, 2)                    = ' . round(8.456, 2) . "\n";
echo '   round(2.5) e round(3.5)            = ' . round(2.5) . ' e ' . round(3.5) . "\n";
echo '   floor(8.9) e ceil(8.1)             = ' . floor(8.9) . ' e ' . ceil(8.1) . "\n";
echo '   number_format(1234.5, 2, ",", ".") = ' . number_format(1234.5, 2, ',', '.') . "\n";
echo '   number_format(1234.5)              = ' . number_format(1234.5) . "\n";

// intdiv arredonda em direcao a menor; cuidado ao dividir numero negativo.
echo "\n7. intdiv trunca para baixo\n";
var_dump(intdiv(7, 2), intdiv(-7, 2), 7 / 2, 7 % 2);

Saída real

1. Os sete operadores aritmeticos
   soma        17 + 5        = 22
   subtracao   17 - 5        = 12
   multiplica   17 * 5       = 85
   divisao      17 / 5        = 3.4
   divisao int  intdiv(17,5)  = 3
   resto        17 % 5        = 2
   potencia     2 ** 10       = 1024

2. / devolve float, intdiv() devolve int
float(3.4)
float(4.25)
int(3)
int(2)

3. O resto % segue o sinal do dividendo
   17 % 5  = 2
   -17 % 5 = -2
   17 % -5 = 2

4. Precedencia e parenteses
   2 + 3 * 4     = 14
   (2 + 3) * 4   = 20
   2 ** 3 ** 2   = 512
   -2 ** 2        = -4

5. O ponto concatena, mas nao e divisao
   1 + 2 . " reais" = 3 reais
   "1" . "2"        = 12
   "1" + "2"        = 3

6. round, floor, ceil e number_format
   round(8.456, 2)                    = 8.46
   round(2.5) e round(3.5)            = 3 e 4
   floor(8.9) e ceil(8.1)             = 8 e 9
   number_format(1234.5, 2, ",", ".") = 1.234,50
   number_format(1234.5)              = 1,235

7. intdiv trunca para baixo
int(3)
int(-3)
float(3.5)
int(1)
Aula 2

Operadores de comparação e lógicos

Operadores de comparação e lógicos

Comparacao devolve bool; os operadores lógicos combinam bool e devolvem bool. O PHP tem duas sintaxes para cada operador lógico: simbolos (&&, ||) e palavras (and, or). Prefira os simbolos, porque as palavras tem precedencia diferente.

Comparacao

OperadorSignificado
> < >= <=maior, menor, maior ou igual, menor ou igual
== !=igual e diferente, convertendo o tipo
=== !==igual e diferente, comparando tipo e valor
<>apelido antigo de !=

String com string compara alfabeticamente, e não por tamanho nem por número: 'Ana' < 'Bruno' e verdadeiro, e '10' < '9' também e verdadeiro, porque '1' vem antes de '9' no código ASCII.

Lógicos

OperadorVerdadeiro quando
!$x$x e falso
$a && $bos dois são verdadeiros
$a or $bpelo menos um e verdadeiro
$a and $bos dois — precedencia menor que a atribuição
$a xor $bexatamente um e verdadeiro
$a &&= $bequivale a $a = $a && $b

O curto-circuito importa: em $formulario !== [] && $formulario['nome'] !== '', se o array estiver vazio a segunda parte nem e avaliada, e o índice inexistente não gera aviso. Esse e o padrao para navegar em array sem isset().

A pegadinha de and e or

As palavras and, or e xor tem precedencia menor que o sinal de igual. Com atribuição do lado esquerdo, o resultado não e o esperado:

$r1 = true and false;   // $r1 vale true  — o and age sobre $r1 e "false"
$r2 = true && false;    // $r2 vale false — && tem prioridade sobre =

O mesmo vale com or: $r = $a or $b não faz $r receber o valor de $a or $b. Se precisar, enclose com parenteses: $r = ($a or $b);.

Juntando condições

&& tem prioridade sobre ||: o grupo com && e resolvido primeiro. Quando a regra e complexa, escreva os parenteses mesmo — a leitura não depende de lembrar a precedencia.

($nota >= 5.0 && $nota < 7.0) && $fezRecuperacao;
$nota >= 7.0 || ($nota >= 5.0 && $fezRecuperacao);

Com negacao, o parenteses muda o resultado, porque ! tem prioridade sobre ==: !$a == true e lido como (!$a) == true. Quando o que se quer negar e a comparacao inteira, escreva !($a == true).

Exemplo

<?php
declare(strict_types=1);

$idade = 17;
$nota = 8.4;
$temAutorizacao = true;
$senhaCorreta = false;

echo "1. Operadores de comparacao\n";
var_dump($idade > 16, $idade >= 18, $nota < 10, $nota <= 8.4, $idade == 17, $idade != 17);
var_dump($senhaCorreta, !$senhaCorreta, !0, !'');

// Tabela-verdade: so false && false e false || false dão resultado diferente
// do esperado. Todo o resto e true.
echo "\n2. Tabela-verdade de && e ||\n";
foreach ([[true, true], [true, false], [false, true], [false, false]] as [$x, $y]) {
    printf(
        "   %-5s && %-5s = %-5s   %-5s || %-5s = %-5s\n",
        var_export($x, true),
        var_export($y, true),
        var_export($x && $y, true),
        var_export($x, true),
        var_export($y, true),
        var_export($x || $y, true)
    );
}

// && e || tem precedencia sobre and e or.
echo "\n3. && tem prioridade; and e or tem prioridade menor\n";
$a = true;
$b = false;
var_dump($a && $b, $a || $b, $a and $b, $a or $b, $a xor $b);

// O detalhe perigoso: and e or tem precedencia MENOR que a atribuicao.
echo "\n4. A pegadinha de and e or com atribuicao\n";
$resultado1 = true and false;
$resultado2 = true && false;
echo '   $r1 = true and false  -> ' . var_export($resultado1, true) . "\n";
echo '   $r2 = true && false  -> ' . var_export($resultado2, true) . "\n";

// && tem precedencia sobre ||: primeiro o grupo com &&, depois o ||.
echo "\n5. Mistura de condicoes: o && e avaliado primeiro\n";
$pontos = 72;
$tentativas = 2;
var_dump($pontos >= 60 && $tentativas < 3);
var_dump($pontos < 60 || $tentativas >= 2);

// A forma correta de montar uma regra com varias condicoes e agrupar com parenteses.
echo "6. Regra real: aprovado E fez a prova de recuperacao\n";
$notaFinal = 5.5;
$fezRecuperacao = true;
var_dump(($notaFinal >= 5.0 && $notaFinal < 7.0) && $fezRecuperacao);
var_export($notaFinal >= 7.0 || ($notaFinal >= 5.0 && $fezRecuperacao));
echo "\n";

// operador ternario logo incluido aqui, mas e assunto do dia 3
echo "7. Condicao resumida em uma linha\n";
echo '   situacao = ' . ($nota >= 7 ? 'aprovado' : ($nota >= 5 ? 'recuperacao' : 'reprovado')) . "\n";

// elseif em vez de if Aninhado: menos chaves e a mesma logica.
echo "\n8. if com elseif le melhor que if aninhado\n";
$opcao = 'b';
if ($opcao === 'a') {
    echo '   escolhido: opcao A' . "\n";
} elseif ($opcao === 'b') {
    echo '   escolhido: opcao B' . "\n";
} else {
    echo '   escolhido: nenhuma' . "\n";
}

Saída real

1. Operadores de comparacao
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)
bool(true)
bool(true)
bool(true)

2. Tabela-verdade de && e ||
   true  && true  = true    true  || true  = true 
   true  && false = false   true  || false = true 
   false && true  = false   false || true  = true 
   false && false = false   false || false = false

3. && tem prioridade; and e or tem prioridade menor
bool(false)
bool(true)
bool(false)
bool(true)
bool(true)

4. A pegadinha de and e or com atribuicao
   $r1 = true and false  -> true
   $r2 = true && false  -> false

5. Mistura de condicoes: o && e avaliado primeiro
bool(true)
bool(true)
6. Regra real: aprovado E fez a prova de recuperacao
bool(true)
true
7. Condicao resumida em uma linha
   situacao = aprovado

8. if com elseif le melhor que if aninhado
   escolhido: opcao B
Aula 3

Funções de string

Funções de string

As funções de string recebem uma string e devolvem string, int ou bool. strlen() conta bytes, e não caracteres: em texto com acento um caractere pode ocupar dois bytes. Para contagem de caracteres, use a familia mb_*.

Medir e limpar

FunçãoAssinaturaO que faz
strlenstrlen(string $texto): intnúmero de bytes
mb_strlenmb_strlen(string $texto, ?string $codificacao = null): intnúmero de caracteres
trimtrim(string $texto, string $caracteres = " \t\n\r"): stringremove espacos das duas pontas
ltrim / rtrimltrim(string $texto, string $caracteres = "..."): stringremove so de um lado
trim('  Maria  ');            // 'Maria'
trim("\t\nTexto\n");          // 'Texto'
trim('xxMariaxx', 'x');       // 'Maria' — segundo argumento e a lista de caracteres

O rtrim() e comum como rtrim($texto, '/') para tirar barra no final de um caminho.

Caixa de letras

FunçãoAssinaturaO que faz
strtolowerstrtolower(string $texto): stringtudo minusculo
strtoupperstrtoupper(string $texto): stringtudo maiusculo
ucfirstucfirst(string $texto): stringso a primeira letra maiuscula
ucwordsucwords(string $texto): stringprimeira letra de cada palavra
mb_strtouppermb_strtoupper(string $texto, ?string $codificacao = null): stringversão multibyte

ucfirst() so mexe na posicao zero: ucfirst('maria silva') devolve 'Maria silva'. Para maiuscula em cada nome, use ucwords(strtolower($nome)).

Procurar, trocar e cortar

FunçãoAssinaturaDevolve
strposstrpos(string $agulha, string $palheiro, int $offset = 0): int ou falseposicao da primeira ocorrencia
strrposstrrpos(string $agulha, string $palheiro, int $offset = 0): int ou falseposicao da ultima ocorrencia
str_containsstr_contains(string $agulha, string $palheiro): boolse contem
str_starts_withstr_starts_with(string $agulha, string $palheiro): boolse comeca com
str_ends_withstr_ends_with(string $agulha, string $palheiro): boolse termina com
strpos($frase, 'PHP');           // 9 — ou false
str_contains($email, '@');       // true
str_starts_with($email, 'maria') // true

strpos() devolve 0 quando acha na primeira posicao, e 0 e falso em contexto booleano. Por isso a comparacao correta e $pos !== false, e não if ($pos).

O parametro offset de strpos() comeca a busca a partir de uma posicao, o que serve para achar a proxima ocorrencia depois da primeira.

FunçãoAssinaturaDevolve
str_replacestr_replace(mixed $procura, mixed $substitui, mixed $texto): mixedtroca todas as ocorrencias
substrsubstr(string $texto, int $inicio, ?int $comprimento = null): stringrecorta a partir da posicao 0
str_padstr_pad(string $texto, int $tamanho, string $preenchimento = " ", int $tipo = STR_PAD_RIGHT): stringcompleta até o tamanho
str_repeatstr_repeat(string $texto, int $vezes): stringrepete

substr() aceita comprimento negativo para cortar a partir do fim: substr($email, -3) devolve os três ultimos caracteres, o equivalente ao .edu.br em um endereco de escola. O str_pad() usa STR_PAD_LEFT para zero a esquerda, como em matricula.

Separar e juntar

explode(',', 'ana,bruno,cecilia');   // ['ana', 'bruno', 'cecilia']
implode(', ', ['ana', 'bruno']);    // 'ana, bruno'

explode() com separador vazio lanca ValueError no PHP 8, e não devolve string vazia. Um texto sem o separador resulta em array de um elemento, e um texto terminado em separador gera um elemento vazio no fim — explode(',', 'a,b,') devolve três itens.

Exemplo

<?php
declare(strict_types=1);

$texto = '  INFORMÁTICA Cavalcanti  ';
$email = '[email protected]';
$frase = 'Curso de PHP 8 na Escola Técnica';

echo "1. strlen conta bytes; para contar caracteres use mb_strlen\n";
var_dump(strlen('Informática'), mb_strlen('Informática'), strlen(''), strlen('ação'));

// trim remove espacos das duas pontas; ltrim e rtrim so de um lado.
echo "\n2. trim remove espacos das duas pontas\n";
var_dump(trim($texto), ltrim($texto), rtrim($texto), trim("\t\nTexto\n"));

echo "\n3. Caixa de letras\n";
echo '   strtolower -> ' . strtolower($texto) . "\n";
echo '   strtoupper -> ' . strtoupper($texto) . "\n";
echo '   ucfirst    -> ' . ucfirst(strtolower($texto)) . "\n";
echo '   ucwords    -> ' . ucwords(strtolower($texto)) . "\n";

echo "\n4. strpos e strrpos devolvem a posicao ou false\n";
var_dump(strpos($frase, 'PHP'), strrpos($frase, 'a'), strpos($frase, 'Java'));

echo "\n5. strpos devolve 0 quando acha na primeira posicao\n";
$pos = strpos('php', 'p');
var_dump($pos, $pos !== false, (bool) $pos);
echo "  -if (\$pos) seria FALSO: 0 e valor falso em contexto booleano\n";

echo "\n6. str_contains, str_starts_with e str_ends_with devolvem bool\n";
var_dump(str_contains($email, '@escola'), str_contains($email, '@'), str_starts_with($email, 'maria'));
var_dump(str_ends_with($email, '.br'), str_ends_with($email, '.edu.br'));

echo "\n7. str_replace troca todas as ocorrencias\n";
echo '   trocou "PHP" por "Python": ' . str_replace('PHP', 'Python', $frase) . "\n";
var_dump(str_replace(['ç', 'ã'], ['c', 'a'], 'coração'));
var_dump(str_replace('escola', 'colégio', 'a escola e a escola'));

echo "\n8. substr conta posicao a partir de zero\n";
var_dump(substr($email, 0, 5), substr($email, 6), substr($email, -3), substr($frase, 0, 9));

echo "\n9. str_pad e str_repeat\n";
var_dump(str_pad('7', 3, '0', STR_PAD_LEFT), str_pad('7', 3, '0', STR_PAD_RIGHT));
var_dump(str_pad('ab', 6, '-', STR_PAD_BOTH), str_repeat('ab', 3));

echo "\n10. explode quebra e implode junta\n";
$lista = explode(',', 'ana,bruno,cecilia,diego');
var_dump($lista);
echo '   implode com virgula: ' . implode(', ', $lista) . "\n";
echo '   implode com barra:  ' . implode(' | ', $lista) . "\n";
var_dump(explode(',', 'a,b,,c'), explode(';', 'a;b'));
// Separator vazio e erro no PHP 8: ValueError na hora, nao string vazia.
echo '   explode com separador vazio seria ValueError no PHP 8' . "\n";

Saída real

1. strlen conta bytes; para contar caracteres use mb_strlen
int(12)
int(11)
int(0)
int(6)

2. trim remove espacos das duas pontas
string(23) "INFORMÁTICA Cavalcanti"
string(25) "INFORMÁTICA Cavalcanti  "
string(25) "  INFORMÁTICA Cavalcanti"
string(5) "Texto"

3. Caixa de letras
   strtolower ->   informÁtica cavalcanti  
   strtoupper ->   INFORMÁTICA CAVALCANTI  
   ucfirst    ->   informÁtica cavalcanti  
   ucwords    ->   InformÁtica Cavalcanti  

4. strpos e strrpos devolvem a posicao ou false
int(9)
int(32)
bool(false)

5. strpos devolve 0 quando acha na primeira posicao
int(0)
bool(true)
bool(false)
  -if ($pos) seria FALSO: 0 e valor falso em contexto booleano

6. str_contains, str_starts_with e str_ends_with devolvem bool
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)

7. str_replace troca todas as ocorrencias
   trocou "PHP" por "Python": Curso de Python 8 na Escola Técnica
string(7) "coracao"
string(23) "a colégio e a colégio"

8. substr conta posicao a partir de zero
string(5) "maria"
string(24) "[email protected]"
string(3) ".br"
string(9) "Curso de "

9. str_pad e str_repeat
string(3) "007"
string(3) "700"
string(6) "--ab--"
string(6) "ababab"

10. explode quebra e implode junta
array(4) {
  [0]=>
  string(3) "ana"
  [1]=>
  string(5) "bruno"
  [2]=>
  string(7) "cecilia"
  [3]=>
  string(5) "diego"
}
   implode com virgula: ana, bruno, cecilia, diego
   implode com barra:  ana | bruno | cecilia | diego
array(4) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(0) ""
  [3]=>
  string(1) "c"
}
array(2) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
}
   explode com separador vazio seria ValueError no PHP 8
Aula 4

Formatação e caracteres especiais

Formatação e caracteres especiais

Estas funções montam texto pronto para exibicao: aplicam casas decimais, separadores de milhar e escapamento de HTML. Formatar e diferente de calcular — o float guarda o valor, a função monta a apresentacao.

sprintf e printf

sprintf() devolve a string montada; printf() imprime direto. Os dois recebem o formato primeiro e os argumentos depois.

sprintf('Aluna %s tirou %.2f', $nome, $nota);
printf('Id %03d | turma %s', 7, 'B');
EspecificadorTipoExemplo
%sstringMaria
%d ou %iinteiro42
%ffloat8.46
%bbinario1010
%ooctal12
%x / %Xhexadecimalff / FF
%ecientifico1.234568e+3

Os números entre % e a letra são o formato: %.2f e duas casas decimais, %03d e inteiro com três digitos preenchidos com zero, %5d e largura minima de cinco, %-5d e os cinco a esquerda. O %% imprime um sinal de porcento.

Cada especificador consome um argumento. Passar menos argumentos que especificadores gera ArgumentCountError.

number_format e padrão brasileiro

number_format(1234.5);                      // 1,235 — padrão en_US
number_format(1234.5, 2);                   // 1,234.50
number_format(1234.5, 2, ',', '.');         // 1.234,50 — padrão brasileiro

A ordem dos argumentos e valor, casas decimais, separador decimal, separador de milhar. O padrao do PHP e o americano, então todo número exibido a um usuario brasileiro precisa dos quatro argumentos. O . separa milhar e a , separa decimal.

Quebra de linha e HTML

FunçãoAssinaturaO que faz
nl2brnl2br(string $texto, bool $use_xhtml = true): stringtroca \n por <br>
strip_tagsstrip_tags(string $texto): stringremove as tags
htmlspecialcharshtmlspecialchars(string $texto, int $flags = ENT_QUOTES, ?string $codificacao = null): stringescapa para texto
nl2br("Linha 1\nLinha 2");                          // quebra vira <br>
strip_tags('<b>Negrito</b>');                       // 'Negrito'
htmlspecialchars('<b>a & b</b>', ENT_QUOTES, 'UTF-8');

strip_tags() apaga a tag e preserva o texto dentro dela; htmlspecialchars() transforma a tag em texto visivel, em vez de esconder como a primeira. Para dado vindo de formulario, o correto e htmlspecialchars() na saída.

O nl2br() age antes de strip_tags(), então esse par não substitui o escapamento.

strlen contra mb_strlen

Todo texto em UTF-8 guarda os acentos em mais de um byte. strlen() conta bytes e mb_strlen() conta caracteres.

Textostrlen()mb_strlen()
PHP33
Programação1110
Informática1110

O substr() também conta bytes, então substr('Programação', 0, 5) devolve Progr — cinco bytes, cinco caracteres — enquanto mb_substr('Programação', 0, 5, 'UTF-8') devolve cinco caracteres inteiros. A função mb_substr() exige o parametro de codificacao na posicao obrigatoria. O erro aparece ao recortar nome com acento: substr() corta no meio da palavra e devolve caractere invalido.

Exemplo

<?php
declare(strict_types=1);

$nome = 'Maria';
$nota = 8.456;
$data = '2026-09-29';
$desconto = 15;

echo "1. sprintf monta o texto e devolve string; printf imprime direto\n";
echo sprintf("   Aluna %s tirou %.2f na prova de %s\n", $nome, $nota, $data);
printf("   Id %03d | turma %s | media %.1f\n", 7, 'B', $nota);

echo "\n2. Especificadores uteis\n";
echo sprintf("   %s     = %s\n", '%s texto', 'abc');
echo sprintf("   %5d    = %5d\n", 42, 42);
echo sprintf("   %-5d|  = %-5d|\n", 42, 42);
echo sprintf("   %05.2f = %05.2f\n", 3.14159, 3.14159);
echo sprintf("   %b %o %x %X = %b %o %x %X\n", 10, 10, 255, 255, 10, 10, 255, 255);
echo sprintf("   %e      = %e\n", 1234.5678, 1234.5678);

echo "\n3. number_format no padrao brasileiro: virgula separa decimal\n";
echo '   padrao en:  ' . number_format(1234.5, 2, '.', ',') . "\n";
echo '   padrao br:  ' . number_format(1234.5, 2, ',', '.') . "\n";
echo '   sem casas:  ' . number_format(1234.5) . "\n";
echo '   milhar:     ' . number_format(1234567.891, 2, ',', '.') . "\n";
echo '   preco final: ' . number_format(1234.5 * (1 - $desconto / 100), 2, ',', '.') . "\n";

echo "\n4. Money e porcentagem com sprintf tambem sai certo\n";
printf("   R$ %s  |  desconto de %d%%\n", number_format(1052.5, 2, ',', '.'), $desconto);

echo "\n5. nl2br troca quebra de linha por <br>\n";
$texto = "Primeira linha\nSegunda linha";
echo '   sem conversao: ' . strlen($texto) . " bytes, nada aparece\n";
echo nl2br($texto) . "\n";
echo '   com <br>:      ' . substr_count(nl2br($texto), '<br') . " tags criadas\n";

echo "\n6. htmlspecialchars escapa o que o navegador interpretaria\n";
$perigoso = '<script>alert("oi")</script> & \'aspas\' e "aspas"';
echo '   cru:        ' . $perigoso . "\n";
echo '   escapado:   ' . htmlspecialchars($perigoso, ENT_QUOTES, 'UTF-8') . "\n";
echo '   so < > &:    ' . htmlspecialchars('<b>a & b</b>', ENT_NOQUOTES, 'UTF-8') . "\n";
echo '   padrao:      ' . htmlspecialchars($perigoso) . "\n";

echo "\n7. strip_tags remove as tags em vez de escapar\n";
echo '   strip_tags: ' . strip_tags('<b>Negrito</b> e <script>codigo()</script>') . "\n";

echo "\n8. strlen conta bytes; mb_* conta caracteres de verdade\n";
$acentuada = 'Programação';
var_dump(strlen($acentuada), mb_strlen($acentuada), strlen($acentuada) === 12);
echo '   substr(0, 10) para antes do problema:           '
    . substr($acentuada, 0, 10) . "\n";
echo '   mb_substr conta caracteres e respeita a palavra: ' . mb_substr($acentuada, 0, 5, 'UTF-8') . "\n";
echo '   Com 11 bytes o PHP corta o "ã" ao meio e a tela mostra um caractere\n';
echo '   quebrado. Por isso o material usa mb_*, e nao substr, em texto.\n';
echo '   mb_strtoupper:            ' . mb_strtoupper($acentuada, 'UTF-8') . "\n";

Saída real

1. sprintf monta o texto e devolve string; printf imprime direto
   Aluna Maria tirou 8.46 na prova de 2026-09-29
   Id 007 | turma B | media 8.5

2. Especificadores uteis
   %s texto     = abc
      42    =    42
   42   |  = 42   |
   03.14 = 03.14
   1010 12 ff FF = 1010 12 ff FF
   1.234568e+3      = 1.234568e+3

3. number_format no padrao brasileiro: virgula separa decimal
   padrao en:  1,234.50
   padrao br:  1.234,50
   sem casas:  1,235
   milhar:     1.234.567,89
   preco final: 1.049,33

4. Money e porcentagem com sprintf tambem sai certo
   R$ 1.052,50  |  desconto de 15%

5. nl2br troca quebra de linha por <br>
   sem conversao: 28 bytes, nada aparece
Primeira linha<br />
Segunda linha
   com <br>:      1 tags criadas

6. htmlspecialchars escapa o que o navegador interpretaria
   cru:        <script>alert("oi")</script> & 'aspas' e "aspas"
   escapado:   &lt;script&gt;alert(&quot;oi&quot;)&lt;/script&gt; &amp; &#039;aspas&#039; e &quot;aspas&quot;
   so < > &:    &lt;b&gt;a &amp; b&lt;/b&gt;
   padrao:      &lt;script&gt;alert(&quot;oi&quot;)&lt;/script&gt; &amp; &#039;aspas&#039; e &quot;aspas&quot;

7. strip_tags remove as tags em vez de escapar
   strip_tags: Negrito e codigo()

8. strlen conta bytes; mb_* conta caracteres de verdade
int(13)
int(11)
bool(false)
   substr(0, 10) para antes do problema:           Programaç
   mb_substr conta caracteres e respeita a palavra: Progr
   Com 11 bytes o PHP corta o "ã" ao meio e a tela mostra um caractere\n   quebrado. Por isso o material usa mb_*, e nao substr, em texto.\n   mb_strtoupper:            PROGRAMAÇÃO