En la continuación de la serie PHP para principiantes, el artículo de hoy se centrará en cómo PHP busca y conecta archivos.
Porque y porque
PHP es un lenguaje de secuencias de comandos creado inicialmente para esculpir rápidamente las páginas de inicio (sí, sí, originalmente era Herramientas de edad personal), y más tarde comenzó a crear tiendas, programas sociales y otras manualidades que iban más allá de lo previsto , pero ¿por qué soy yo? Y el hecho de que mientras más funcionalidad está codificada, mayor es el deseo de estructurarlo correctamente, deshacerse de la duplicación de código, dividirlo en partes lógicas y conectarse solo si es necesario (esta es la misma sensación que tenía cuando lo leíste antes posición, podría romperse en pedazos separados). Para este propósito, PHP tiene varias funciones, cuyo significado general es conectar e interpretar el archivo especificado. Veamos un ejemplo de conexión de archivos:
Si ejecuta el script
index.php , PHP se conectará y ejecutará todo esto en secuencia:
$a = 0; $a++; $a++; echo $a;
Cuando se conecta un archivo, su código tiene el mismo alcance que la línea en la que se conectó, por lo que todas las variables disponibles en esta línea estarán disponibles en el archivo incluido. Si se declararon clases o funciones en el archivo de inclusión, caen en el ámbito global (a menos, por supuesto, que se especifique un espacio de nombres para ellas).
Si conecta el archivo dentro de la función, los archivos incluidos obtienen acceso al alcance de la función, por lo que el siguiente código también funcionará:
function() { $a = 0; include ('increment.php'); include ('increment.php'); echo $a; } a();
Por separado, noto las constantes mágicas : __DIR__
, __FILE__
, __LINE__
y otras: están vinculadas al contexto y se ejecutan antes de que ocurra la inclusión
La peculiaridad de conectar archivos es que al conectar un archivo, el análisis cambia al modo HTML, por esta razón, cualquier código dentro del archivo incluido debe estar encerrado en etiquetas PHP:
<?php
Si solo tiene código PHP en el archivo, es costumbre omitir la etiqueta de cierre, para no olvidar accidentalmente ningún hilo de caracteres después de la etiqueta de cierre, que está plagado de problemas (lo discutiré en el próximo artículo).
¿Has visto un archivo de sitio con 10,000 líneas? Ya tengo lágrimas en los ojos (╥_╥) ...
Funciones de conexión de archivos
Como se mencionó anteriormente, en PHP hay varias funciones para conectar archivos:
- include - incluye y ejecuta el archivo especificado, si no lo encuentra - emite una alerta
E_WARNING
- include_once : similar a la función anterior, pero incluye el archivo una vez
- require - incluye y ejecuta el archivo especificado, si no lo encuentra - da un error fatal
E_ERROR
- require_once : similar a la función anterior, pero incluye el archivo una vez
En realidad, estas no son exactamente funciones, son construcciones de lenguaje especiales y se pueden omitir paréntesis. Entre otras cosas, hay otras formas de conectar y ejecutar archivos, pero excave usted mismo, deje que sea una "tarea con un asterisco" para usted;)
Tomemos un ejemplo de las diferencias entre
require
y
require_once
, tome un archivo
echo.php :
<p>text of file echo.php</p>
Y lo conectaremos varias veces:
<?php
El resultado de la ejecución serán dos conexiones al archivo
echo.php :
<p>text of file echo.php</p> <p>text of file echo.php</p>
Hay un par de directivas más que afectan la conexión, pero no las necesitará:
auto_prepend_file y
auto_append_file . Estas directivas le permiten instalar archivos que se conectarán antes de que se conecten todos los archivos y después de que se ejecuten todos los scripts, respectivamente. Ni siquiera se me ocurre un escenario "en vivo" cuando puede ser necesario.
TareaPuede crear e implementar un script para usar las
auto_append_file
auto_prepend_file
y
auto_append_file
, solo puede cambiarlas en
php.ini ,
.htaccess o
httpd.conf (consulte
PHP_INI_PERDIR ) :)
Donde esta mirando
PHP busca archivos de inclusión en directorios especificados en la directiva
include_path . Esta directiva también afecta el funcionamiento de
fopen()
,
file()
,
readfile()
y
file_get_contents()
. El algoritmo es bastante simple: cuando busca archivos, PHP se turna para verificar cada directorio desde
include_path
, hasta que encuentra un archivo para conectarse, si no lo hace, devuelve un error. Para cambiar
include_path
desde un script, use la función
set_include_path () .
Hay una cosa importante a tener en cuenta al configurar
include_path
: se utilizan diferentes caracteres como separador de ruta en Windows y Linux: ";" y ":" respectivamente, así que cuando especifique su directorio, use la constante
PATH_SEPARATOR
, por ejemplo:
Cuando escribe
include_path
en un archivo ini, puede usar variables de entorno como
${USER}
:
include_path = ".:${USER}/my-php-library"
Si incluye una ruta absoluta (que comienza con "/") o relativa (que comienza con "." O "..") al conectar el archivo, la directiva
include_path
se ignorará y la búsqueda se realizará solo en la ruta especificada.
Quizás valga la pena hablar sobre safe_mode , pero esta ha sido una historia desde hace mucho tiempo (desde la versión 5.4), y espero que no la encuentres, pero si de repente, sabes qué fue, pero pasó ...
Usando retorno
Te contaré sobre un pequeño truco de vida: si el archivo incluido devuelve algo usando la construcción de
return
, entonces estos datos se pueden obtener y usar, para que puedas organizar fácilmente la conexión de los archivos de configuración, daré un ejemplo a modo de ilustración:
return [ 'host' => 'localhost', 'user' => 'root', 'pass' => '' ];
$dbConfig = require 'config/db.php'; var_dump($dbConfig);
Datos interesantes, sin los cuales también fue bueno: si las funciones se definen en el archivo incluido, entonces se pueden usar en el archivo principal, independientemente de si se declararon antes o después de la devolución
TareaEscriba el código que recopilará la configuración de varias carpetas y archivos. La estructura del archivo es la siguiente:
config |-- default | |-- db.php | |-- debug.php | |-- language.php | `-- template.php |-- development | `-- db.php `-- production |-- db.php `-- language.php
En este caso, el código debería funcionar de la siguiente manera:
- si hay una variable
PROJECT_PHP_SERVER
en el entorno del sistema y es igual al development
, entonces todos los archivos de la carpeta predeterminada deben estar conectados, los datos deben incluirse en la variable $config
, los archivos de la carpeta de desarrollo deben estar conectados y los datos recibidos deben moler los elementos correspondientes almacenados en $config
- comportamiento similar si
PROJECT_PHP_SERVER
es production
(naturalmente solo para la carpeta de producción ) - Si no hay una variable o está configurada incorrectamente, solo se conectan los archivos de la carpeta predeterminada
Conexión automática
Las construcciones con archivos adjuntos se ven muy voluminosas, y también siguen su actualización: otro regalo, consulte un fragmento de código del
artículo de ejemplo
sobre excepciones :
El primer intento de evitar tal "felicidad" fue la aparición de la función
__autoload . Más precisamente, ni siquiera era una función específica, tenía que definir esta función usted mismo, y con ella necesitaba conectar los archivos que necesitábamos por el nombre de la clase. La única regla era que
para cada clase se debería crear un archivo separado con el nombre de la clase (es decir,
myClass debería estar dentro del archivo
myClass.php ). Aquí hay un ejemplo de la implementación de dicha función
__autoload()
(tomada de los comentarios en el manual oficial):
La clase que conectaremos:
El archivo que conecta esta clase:
Ahora sobre los problemas con esta función: imagine una situación en la que está conectando código de terceros, y allí alguien ya ha registrado la
__autoload()
para su código, y listo:
Fatal error: Cannot redeclare __autoload()
Para evitar esto, creamos una función que le permite registrar una función o método arbitrario como un cargador de clases:
spl_autoload_register . Es decir podemos crear varias funciones con un nombre arbitrario para cargar clases y registrarlas usando
spl_autoload_register
. Ahora
index.php
se verá así:
El encabezado "¿sabía?": El primer parámetro spl_autoload_register()
es opcional, y al llamar a la función sin él, la función spl_autoload se usará como cargador, la búsqueda se realizará en carpetas de include_path
y archivos con la extensión .php
y .inc
, pero esto la lista se puede expandir usando la función spl_autoload_extensions
Ahora cada desarrollador puede registrar su cargador, lo principal es que los nombres de las clases no coinciden, pero esto no debería ser un problema si usa espacios de nombres.
Dado que una funcionalidad tan avanzada como spl_autoload_register()
ha existido durante mucho tiempo, la spl_autoload_register()
ya está declarada en desuso en PHP 7.1 , lo que significa que en el futuro previsible esta función se eliminará por completo (X_x)
Bueno, la imagen se ha aclarado más o menos, aunque oye, todos los gestores de arranque registrados se ponen en cola a medida que se registran, respectivamente, si alguien lo ha engañado en su gestor de arranque, entonces, en lugar del resultado esperado, resultará un error muy desagradable. Para evitar esto, los chicos inteligentes adultos han descrito un estándar que le permite conectar bibliotecas de terceros sin problemas, lo principal es que la organización de las clases en ellos cumple con el estándar
PSR-0 (ya tiene 10 años) o
PSR-4 . ¿Cuál es la esencia de los requisitos descritos en las normas?
- Cada biblioteca debe vivir en su propio espacio de nombres (el denominado espacio de nombres del proveedor)
- Cada espacio de nombres debe tener su propia carpeta.
- Dentro del espacio de nombres puede haber subespacios, también en carpetas separadas
- Una clase - un archivo
- El nombre del archivo con la extensión
.php
debe coincidir exactamente con el nombre de la clase
Ejemplo del manual:
Nombre completo de clase | Espacio de nombres | Directorio base | Camino 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 | / ruta / a / 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 / incluye / Zend / | /usr/includes/Zend/Acl.php |
Las diferencias entre estos dos estándares son que PSR-0 admite código antiguo sin un espacio de nombres (es decir, anterior a la versión 5.3.0), y PSR-4 está libre de este anacronismo e incluso evita el anidamiento innecesario de carpetas.
Gracias a estos estándares, se hizo posible la aparición de una herramienta como el
compositor , un administrador de paquetes universal para PHP. Si alguien se perdió, entonces hay un buen informe de
pronskiy sobre esta herramienta.
Inyección de php
También quería hablar sobre el primer error de todos los que hacen un único punto de entrada para el sitio en un
index.php
y lo llaman el marco MVC:
<?php $page = $_GET['page'] ?? die('Wrong filename'); if (!is_file($page)) { die('Wrong filename'); } include $page;
Miras el código y solo quieres transferir algo malicioso allí:
// 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
Lo primero que viene a la mente es agregar con fuerza la extensión
.php
, pero en algunos casos se puede eludir "gracias a" una
vulnerabilidad de cero bytes (leer esta vulnerabilidad se
ha solucionado durante
mucho tiempo , pero de repente te encuentras con un intérprete anterior a PHP 5.3, bueno, por desarrollo general también se recomienda):
// http://domain.com/index.php?page=/etc/passwd%00
En las versiones modernas de PHP, la presencia de un carácter de cero bytes en la ruta del archivo conectado conduce inmediatamente al error de conexión correspondiente, e incluso si el archivo especificado existe y puede conectarse, el resultado siempre será un error, se verifica de la siguiente manera strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename)
(esto es de las entrañas de PHP)
El segundo pensamiento "que vale la pena" es buscar un archivo en el directorio actual:
<?php $page = $_GET['page'] ?? die('Wrong filename'); if (strpos(realpath($page), __DIR__) !== 0) { die('Wrong path to file'); } include $page . '.php';
La tercera, pero no la última modificación de la verificación es el uso de la directiva
open_basedir , con su ayuda puede especificar el directorio donde exactamente PHP buscará los archivos para conectarse:
<?php $page = $_GET['page'] ?? die('Wrong filename'); ini_set('open_basedir', __DIR__); include $page . '.php';
Tenga cuidado, esta directiva afecta no solo la conexión de archivos, sino que también todos funcionan con el sistema de archivos, es decir incluida esta restricción, debe asegurarse de no haber olvidado nada fuera del directorio especificado, ni los datos en caché, ni ningún archivo de usuario (aunque las funciones is_uploaded_file()
y move_uploaded_file()
continuarán funcionando con una carpeta temporal para los archivos descargados).
¿Qué otros controles son posibles? Muchas opciones, todo depende de la arquitectura de su aplicación.
También quería recordar la existencia de una directiva "maravillosa"
allow_url_include (depende de
allow_url_fopen ), que le permite conectarse y ejecutar archivos PHP remotos, lo que es mucho más peligroso para su servidor:
Vio, recuerde y nunca use, el beneficio está desactivado por defecto. Necesitará esta característica un poco menos que nunca; en todos los demás casos, coloque la arquitectura de aplicación correcta, donde varias partes de la aplicación se comunican a través de la API.
TareaEscriba una secuencia de comandos que le permita conectar las secuencias de comandos php de la carpeta actual por su nombre, mientras recuerda las posibles vulnerabilidades y evita errores.
En conclusión
Este artículo es la base de PHP, así que estudie cuidadosamente, complete las tareas y no presente archivos, nadie le enseñará.
PS
Este es un
reenvío de una serie de artículos "PHP para principiantes":
Si tiene comentarios sobre el material del artículo, o posiblemente en forma, entonces describa la esencia en los comentarios, y haremos que este material sea aún mejor.