Na continuação da série PHP para iniciantes, o artigo de hoje se concentrará em como o PHP pesquisa e conecta arquivos.
Porque e porque
O PHP é uma linguagem de script criada inicialmente para esculpir rapidamente as páginas iniciais (sim, sim, originalmente era Ferramentas Pessoais de Idade Pessoal) e, mais tarde, começou a criar lojas, programas sociais e outros artesanatos que vão além do que se pretendia. , mas por que eu estou - e, quanto mais funcionalidade é codificada, maior é o desejo de estruturá-la corretamente, livrar-se da duplicação de código, dividi-la em partes lógicas e conectar-se apenas se necessário (esse é o mesmo sentimento que você teve quando você leu antes posição, poderia ser quebrado em pedaços separados). Para esse propósito, o PHP tem várias funções, cujo significado geral é conectar e interpretar o arquivo especificado. Vejamos um exemplo de conexão de arquivos:
Se você executar o script
index.php , o PHP conectará e executará tudo isso em sequência:
$a = 0; $a++; $a++; echo $a;
Quando um arquivo é conectado, seu código está no mesmo escopo da linha em que foi conectado, portanto, todas as variáveis disponíveis nessa linha estarão disponíveis no arquivo incluído. Se classes ou funções foram declaradas no arquivo de inclusão, elas se enquadram no escopo global (a menos que, é claro, um espaço para nome tenha sido especificado para elas).
Se você conectar o arquivo dentro da função, os arquivos incluídos terão acesso ao escopo da função, portanto o código a seguir também funcionará:
function() { $a = 0; include ('increment.php'); include ('increment.php'); echo $a; } a();
Separadamente, observo as constantes mágicas : __DIR__
, __FILE__
, __LINE__
e outras - elas estão vinculadas ao contexto e são executadas antes da inclusão ocorrer
A peculiaridade de conectar arquivos é que, ao conectar um arquivo, a análise muda para o modo HTML; por esse motivo, qualquer código dentro do arquivo incluído deve estar entre tags PHP:
<?php
Se você tiver apenas código PHP no arquivo, é costume omitir a tag de fechamento, para não esquecer acidentalmente qualquer encadeamento de caracteres após a tag de fechamento, o que está repleto de problemas (discutirei isso no próximo artigo).
Você viu um arquivo de site com 10.000 linhas? Já lágrimas nos meus olhos (╥_╥) ...
Recursos de conexão de arquivo
Como mencionado acima, no PHP existem várias funções para conectar arquivos:
- incluir - inclui e executa o arquivo especificado, se não o encontrar - emite um alerta
E_WARNING
- include_once - semelhante à função acima, mas inclui o arquivo uma vez
- exigir - inclui e executa o arquivo especificado, se não o encontrar - gera um erro fatal
E_ERROR
- require_once - semelhante à função acima, mas inclui o arquivo uma vez
Na realidade, essas não são exatamente funções, são construções especiais de linguagem e parênteses podem ser omitidos. Entre outras coisas, existem outras maneiras de conectar e executar arquivos, mas cave você mesmo, que seja uma "tarefa com um asterisco" para você;)
Vamos dar um exemplo das diferenças entre
require
e
require_once
, pegue um arquivo
echo.php :
<p>text of file echo.php</p>
E vamos conectá-lo várias vezes:
<?php
O resultado da execução serão duas conexões com o arquivo
echo.php :
<p>text of file echo.php</p> <p>text of file echo.php</p>
Existem algumas diretivas que afetam a conexão, mas você não precisará delas -
auto_prepend_file e
auto_append_file . Essas diretivas permitem instalar arquivos que serão conectados antes de todos os arquivos serem conectados e após a execução de todos os scripts, respectivamente. Não consigo nem imaginar um cenário "ao vivo" quando necessário.
TarefaVocê pode criar e implementar um script para usar as
auto_prepend_file
e
auto_append_file
; você só pode alterá-las no
php.ini ,
.htaccess ou
httpd.conf (consulte
PHP_INI_PERDIR ) :)
Para onde está olhando?
O PHP procura arquivos de inclusão nos diretórios especificados na diretiva
include_path . Essa diretiva também afeta a operação de
fopen()
,
file()
,
readfile()
e
file_get_contents()
. O algoritmo é bastante simples - ao pesquisar por arquivos, o PHP revisa cada diretório a partir de
include_path
, até encontrar um arquivo para conectar, caso contrário, retorna um erro. Para alterar o
include_path
de um script, use a função
set_include_path () .
Há uma coisa importante a considerar ao configurar
include_path
- caracteres diferentes são usados como separador de caminho no Windows e Linux - ";" e ":" respectivamente, portanto, ao especificar seu diretório, use a constante
PATH_SEPARATOR
, por exemplo:
Ao escrever
include_path
em um arquivo ini, você pode usar variáveis de ambiente como
${USER}
:
include_path = ".:${USER}/my-php-library"
Se você incluir um caminho absoluto (começando com "/") ou relativo (começando com "." Ou "..") ao conectar o arquivo, a diretiva
include_path
será ignorada e a pesquisa será realizada apenas no caminho especificado.
Talvez valha a pena falar sobre safe_mode , mas isso tem sido uma história (desde a versão 5.4), e espero que você não a encontre, mas de repente, para saber o que era, mas passou ...
Usando retorno
Vou falar sobre um pequeno truque de vida - se o arquivo incluído retornar algo usando a construção de
return
, esses dados poderão ser obtidos e usados, para que você possa organizar facilmente a conexão dos arquivos de configuração, darei um exemplo para ilustrar:
return [ 'host' => 'localhost', 'user' => 'root', 'pass' => '' ];
$dbConfig = require 'config/db.php'; var_dump($dbConfig);
Fatos interessantes, sem os quais também foi bom: se as funções forem definidas no arquivo incluído, elas poderão ser usadas no arquivo principal, independentemente de terem sido declaradas antes ou depois do retorno
TarefaEscreva o código que coletará a configuração de várias pastas e arquivos. A estrutura do arquivo é a seguinte:
config |-- default | |-- db.php | |-- debug.php | |-- language.php | `-- template.php |-- development | `-- db.php `-- production |-- db.php `-- language.php
Nesse caso, o código deve funcionar da seguinte maneira:
- se houver uma variável
PROJECT_PHP_SERVER
no ambiente do sistema e for igual ao development
, todos os arquivos da pasta padrão deverão ser conectados, os dados deverão ser incluídos na variável $config
, os arquivos da pasta de desenvolvimento deverão ser conectados e os dados recebidos deverão moer os itens correspondentes armazenados em $config
- comportamento semelhante se
PROJECT_PHP_SERVER
for production
(naturalmente apenas para a pasta de produção ) - se não houver variável ou estiver definido incorretamente, apenas os arquivos da pasta padrão serão conectados
Conexão automática
As construções com anexos de arquivo parecem muito volumosas e também seguem a atualização - outro presente, confira um trecho de código do
artigo de exemplo
sobre exceções :
A primeira tentativa de evitar essa "felicidade" foi o aparecimento da função
__autoload . Mais precisamente, nem era uma função específica, você tinha que definir essa função e, com ela, precisava conectar os arquivos que precisávamos pelo nome da classe. A única regra era que,
para cada classe, um arquivo separado fosse criado pelo nome da classe (ou seja,
myClass deveria estar dentro do arquivo
myClass.php ). Aqui está um exemplo da implementação dessa função
__autoload()
(extraída de comentários no manual oficial):
A classe que iremos conectar:
O arquivo que conecta esta classe:
Agora, sobre os problemas com essa função - imagine uma situação em que você esteja conectando código de terceiros, e aí alguém já registrou a função
__autoload()
para o seu código e pronto:
Fatal error: Cannot redeclare __autoload()
Para evitar isso, criamos uma função que permite registrar uma função ou método arbitrário como um carregador de classes -
spl_autoload_register . I.e. podemos criar várias funções com um nome arbitrário para carregar classes e registrá-las usando
spl_autoload_register
. Agora, o
index.php
ficará assim:
O cabeçalho “você sabia?”: O primeiro parâmetro spl_autoload_register()
é opcional e, chamando a função sem ela, a função spl_autoload será usada como carregador, a pesquisa será realizada em pastas de include_path
e arquivos com a extensão .php
e .inc
, mas isso a lista pode ser expandida usando a função spl_autoload_extensions
Agora, cada desenvolvedor pode registrar seu carregador, o principal é que os nomes das classes não coincidem, mas isso não deve ser um problema se você usar espaços para nome.
Como uma funcionalidade avançada como spl_autoload_register()
existe há muito tempo, a função spl_autoload_register()
já está declarada obsoleta no PHP 7.1 , o que significa que, no futuro próximo, essa função será completamente removida (X_x)
Bem, a imagem ficou mais ou menos esclarecida, embora, ei, todos os gerenciadores de inicialização registrados estejam na fila da mesma forma que são registrados, respectivamente, se alguém o enganou em seu gerenciador de inicialização, em vez do resultado esperado, um erro muito desagradável será solucionado. Para evitar isso, os adultos espertos descreveram um padrão que permite conectar bibliotecas de terceiros sem problemas, o principal é que a organização das classes neles está em conformidade com o padrão
PSR-0 (já tem 10 anos) ou
PSR-4 . Qual é a essência dos requisitos descritos nas normas:
- Cada biblioteca deve viver em seu próprio espaço para nome (o chamado espaço para nome do fornecedor)
- Cada espaço para nome deve ter sua própria pasta.
- Dentro do espaço para nome, pode haver subespaços - também em pastas separadas
- Uma classe - um arquivo
- O nome do arquivo com a extensão
.php
deve corresponder exatamente ao nome da classe
Exemplo do manual:
Nome completo da turma | Namespace | Diretório base | Caminho completo |
---|
\ Acme \ Log \ Writer \ File_Writer | Acme \ Log \ Writer | ./acme-log-writer/lib/ | ./acme-log-writer/lib/File_Writer.php |
\ Aura \ Web \ Response \ Status | Aura \ Web | / caminho / para / aura-web / src / | /path/to/aura-web/src/Response/Status.php |
\ Symfony \ Core \ Request | Symfony \ core | ./vendor/Symfony/Core/ | ./vendor/Symfony/Core/Request.php |
\ Zend \ Acl | Zend | / usr / includes / Zend / | /usr/includes/Zend/Acl.php |
As diferenças entre esses dois padrões são que o PSR-0 suporta código antigo sem um namespace (ou seja, anterior à versão 5.3.0), e o PSR-4 está livre desse anacronismo e até evita o aninhamento desnecessário de pastas.
Graças a esses padrões, tornou-se possível o surgimento de uma ferramenta como
compositor - um gerenciador de pacotes universal para PHP. Se alguém errou, há um bom relatório da
pronskiy sobre essa ferramenta.
Injeção de php
Eu também queria falar sobre o primeiro erro de todo mundo que cria um único ponto de entrada para o site em um
index.php
e o chama de framework MVC:
<?php $page = $_GET['page'] ?? die('Wrong filename'); if (!is_file($page)) { die('Wrong filename'); } include $page;
Você olha o código e deseja apenas transferir algo malicioso para lá:
// http://domain.com/index.php?page=../index.php // http://domain.com/index.php?page=config.ini // http://domain.com/index.php?page=/etc/passwd // , http://domain.com/index.php?page=user/backdoor.php
A primeira coisa que vem à mente é adicionar com força a extensão
.php
, mas em alguns casos ela pode ser contornada "graças a" uma
vulnerabilidade de zero byte (leia esta vulnerabilidade
foi corrigida por um
longo tempo , mas de repente você encontra um intérprete anterior ao PHP 5.3, bem, por desenvolvimento geral também recomendam):
// http://domain.com/index.php?page=/etc/passwd%00
Nas versões modernas do PHP, a presença de um caractere de byte zero no caminho do arquivo conectado imediatamente leva ao erro de conexão correspondente e, mesmo que o arquivo especificado exista e possa ser conectado, o resultado sempre será um erro, ele é verificado da seguinte maneira strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename)
(isto é das entranhas do próprio PHP)
O segundo pensamento "que vale a pena" é procurar um arquivo no diretório atual:
<?php $page = $_GET['page'] ?? die('Wrong filename'); if (strpos(realpath($page), __DIR__) !== 0) { die('Wrong path to file'); } include $page . '.php';
A terceira, mas não a última modificação da verificação é o uso da diretiva
open_basedir , com sua ajuda, você pode especificar o diretório em que exatamente o PHP procurará arquivos para conectar:
<?php $page = $_GET['page'] ?? die('Wrong filename'); ini_set('open_basedir', __DIR__); include $page . '.php';
Tenha cuidado, esta diretiva afeta não apenas a conexão de arquivos, mas também todos funcionam com o sistema de arquivos, ou seja, incluindo essa restrição, você deve ter esquecido nada fora do diretório especificado, nem os dados armazenados em cache nem os arquivos do usuário (embora as funções is_uploaded_file()
e move_uploaded_file()
continuem funcionando com uma pasta temporária para os arquivos baixados).
Que outras verificações são possíveis? Muitas opções, tudo depende da arquitetura do seu aplicativo.
Eu também queria lembrar a existência de uma diretiva "maravilhosa"
allow_url_include (depende de
allow_url_fopen ), que permite conectar e executar arquivos PHP remotos, o que é muito mais perigoso para o seu servidor:
Vi, lembre-se e nunca use, o benefício é desativado por padrão. Você precisará desse recurso um pouco menos do que nunca; em todos os outros casos, estabeleça a arquitetura correta do aplicativo, onde várias partes do aplicativo se comunicam por meio da API.
TarefaEscreva um script que permita conectar scripts php da pasta atual por nome, lembrando-se de possíveis vulnerabilidades e evitando erros.
Em conclusão
Este artigo é a base do PHP, portanto, estude cuidadosamente, conclua tarefas e não arquive, ninguém ensinará para você.
PS
Este é um
repost de uma série de artigos "PHP para iniciantes":
Se você tiver comentários sobre o material do artigo, ou possivelmente em forma, descreva a essência dos comentários, e tornaremos esse material ainda melhor.