Solo el que no hace nada no comete errores, y somos un ejemplo de esto: nos sentamos y trabajamos incansablemente, leemos el Habr :)
En este artículo, dirigiré mi historia sobre errores en PHP y cómo frenarlos.
Errores
Variedades en la familia del error
Antes de domesticar errores, recomendaría estudiar
cada especie y prestar atención por separado a los representantes más destacados.
Para evitar que un solo error pase desapercibido, debe habilitar el seguimiento de todos los errores utilizando la función
error_reporting () y la directiva
display_errors para habilitar su visualización:
<?php error_reporting(E_ALL); ini_set('display_errors', 1);
Errores fatales
Los tipos de errores más formidables son fatales, pueden ocurrir tanto durante la compilación como durante el trabajo del analizador o script PHP, mientras el script se interrumpe.
E_PARSEEste error aparece cuando comete un error de sintaxis grave y el intérprete PHP no entiende lo que quiere de él, por ejemplo, si no cerró el rizado o el paréntesis:
<?php {
O escribieron en un lenguaje incomprensible:
<?php
También aparecen corchetes adicionales, y no tan importantes redondos o rizados:
<?php }
Observo un punto importante: el código del archivo en el que cometió el error de análisis no se ejecutará, por lo tanto, si intenta activar la visualización de errores en el mismo archivo donde ocurrió el error del analizador, esto no funcionará:
<?php
E_ERROREste error aparece cuando PHP entendió lo que desea, pero esto no funcionó por varias razones. Este error también interrumpe la ejecución del script y el código funcionará antes de que aparezca el error:
No se encontró el complemento:
require_once 'not-exists.php';
Se lanzó una excepción (qué tipo de bestia, te contaré un poco más tarde), pero no se procesó:
throw new Exception();
Al intentar llamar a un método de clase inexistente:
$stdClass = new stdClass(); $stdClass->notExists();
Falta de memoria libre (más de lo prescrito en la directiva
memory_limit ) o algo similar:
$arr = array(); while (true) { $arr[] = str_pad(' ', 1024); }
Es muy común al leer o descargar archivos grandes, así que tenga cuidado con el problema del consumo de memoria.
Llamada de función recursiva. En este ejemplo, terminó en la iteración 256, porque está escrito
en la configuración de xdebug (sí, este error puede aparecer en este formulario solo cuando la extensión xdebug está habilitada):
function deep() { deep(); } deep();
No fatal
Esta vista no interrumpe la ejecución del script, pero es el probador quien generalmente los encuentra. Son estos errores los que causan más problemas a los desarrolladores novatos.
E_ADVERTENCIAA menudo ocurre cuando conecta un archivo usando
include
, pero no aparece en el servidor o si cometió un error al indicar la ruta al archivo:
include_once 'not-exists.php';
Ocurre si usa el tipo incorrecto de argumentos al llamar a funciones:
join('string', 'string');
Hay muchos de ellos, y enumerar todo no tiene sentido ...
E_NOTICEEstos son los errores más comunes, además, hay ventiladores que apagan la salida de errores y los remachan todo el día. Hay una serie de errores triviales.
Al acceder a una variable indefinida:
echo $a;
Al acceder a un elemento de matriz inexistente:
$b = []; $b['a'];
Al acceder a una constante inexistente:
echo UNKNOWN_CONSTANT;
Cuando los tipos de datos no se convierten:
echo array();
Para evitar tales errores, tenga cuidado, y si el IDE le dice algo, no lo ignore:
E_STRICTEstos son errores que le enseñarán a escribir el código correctamente para que no se avergüence, especialmente porque el IDE le muestra inmediatamente estos errores. Por ejemplo, si llamó a un método no estático como estático, el código funcionará, pero de alguna manera es incorrecto, y pueden producirse errores graves si se cambia el método de clase en el futuro, y aparece una apelación a
$this
:
class Strict { public function test() { echo "Test"; } } Strict::test();
Este tipo de error es relevante para PHP versión 5.6, y casi todos fueron eliminados
7 partidos. Lea más en el RFC relevante . Si alguien sabe dónde quedaron estos errores, escriba los comentarios
E_DEPRECATEDEntonces PHP jurará si usa funciones obsoletas (es decir, aquellas que están marcadas como obsoletas, y no estarán en la próxima versión principal):
En mi editor, se tacharán funciones similares:

Personalizado
Este tipo, que el desarrollador de código mismo "engendra", no los he visto en mucho tiempo, y no recomiendo que abusen de ellos:
E_USER_ERROR
- error críticoE_USER_WARNING
: no es un error críticoE_USER_NOTICE
: mensajes que no son errores
Por separado, vale la pena señalar
E_USER_DEPRECATED
: este tipo todavía se usa con mucha frecuencia para recordarle al programador que el método o la función están desactualizados y es hora de volver a escribir el código sin usarlo. La función
trigger_error () se usa para crear este y otros errores similares:
function generateToken() { trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED);
Ahora que se ha familiarizado con la mayoría de los tipos y tipos de errores, es hora de expresar una breve explicación sobre el funcionamiento de la directiva display_errors
:
- si
display_errors = on
, entonces, en caso de error, el navegador recibirá html con el texto de error y el código 200 - si
display_errors = off
, para errores fatales el código de respuesta será 500 y el resultado no será devuelto al usuario, para otros errores: el código no funcionará correctamente, pero no se lo dirá a nadie
Domar
Hay 3 funciones para trabajar con errores en PHP:
- set_error_handler () : establece un controlador para errores que no interrumpen el script (es decir, para errores no fatales)
- error_get_last () - obtiene información sobre el último error
- register_shutdown_function () : registra un controlador que se iniciará cuando finalice el script. Esta función no se aplica directamente a los controladores de errores, pero a menudo se usa para este propósito.
Ahora, algunos detalles sobre el manejo de errores usando
set_error_handler()
, como argumentos, esta función toma el nombre de la función que se le asignará la misión de manejo de errores y los
tipos de errores que serán monitoreados. Un controlador de errores también puede ser un método de clase o una función anónima, lo principal es que toma la siguiente lista de argumentos:
$errno
: el primer argumento contiene el tipo de error como un entero$errstr
: el segundo argumento contiene un mensaje de error$errfile
: un tercer argumento opcional contiene el nombre del archivo en el que ocurrió el error$errline
: un cuarto argumento opcional contiene el número de línea en el que ocurrió el error$errcontext
: el quinto argumento opcional contiene una matriz de todas las variables existentes en el ámbito donde ocurrió el error
Si el controlador devuelve
true
, entonces el error se considerará procesado y el script continuará ejecutándose; de lo contrario, se llamará a un controlador estándar que registra el error y, según su tipo, continuará ejecutando el script o completándolo. Aquí hay un ejemplo de controlador:
<?php
No podrá asignar más de una función para manejar errores, aunque me gustaría registrar mi propio controlador para cada tipo de error, pero no, escriba un controlador y describa la lógica de visualización completa para cada tipo directamente en él.
Hay un problema importante con el controlador que está escrito anteriormente: no detecta errores fatales y, con dichos errores, en lugar del sitio, los usuarios verán solo una página en blanco o, lo que es peor, un mensaje de error. Para evitar este escenario, debe usar la función
register_shutdown_function () y usarla para registrar una función que siempre se ejecutará al final del script:
function shutdown() { echo ' '; } register_shutdown_function('shutdown');
¡Esta función siempre funcionará!
Pero volviendo a los errores, para rastrear la aparición del error en el código de error, utilizamos la función
error_get_last () , con su ayuda puede obtener información sobre el último error detectado, y dado que los errores fatales interrumpen la ejecución del código, siempre desempeñarán el papel de "último":
function shutdown() { $error = error_get_last(); if (
Me gustaría llamar la atención sobre el hecho de que este código incluso se produce para el manejo de errores, e incluso puede encontrarlo, pero ha perdido relevancia desde la séptima versión de PHP. Lo que vino a reemplazar lo contaré un poco más tarde.
TareaComplemente el controlador de errores fatales con la salida del código fuente del archivo donde se cometió el error, y también agregue el resaltado de sintaxis del código de salida.
Sobre la gula
Hagamos una prueba simple y descubramos cuántos recursos preciosos come el error más trivial:
Como resultado de ejecutar este script, obtuve este resultado:
0.002867 seconds
Ahora agregue el error en el bucle:
El resultado es peor, y un orden de magnitud (¡incluso dos órdenes de magnitud!):
0.263645 seconds
La conclusión es clara: los errores en el código conducen a una excesiva glotonería de los scripts, ¡así que encienda la visualización de todos los errores durante el desarrollo y las pruebas de la aplicación!Las pruebas se llevaron a cabo en varias versiones de PHP, y en todas partes la diferencia es decenas de veces, así que esta es otra razón para corregir todos los errores en el código
¿Dónde está enterrado el perro?
PHP tiene un símbolo especial "@": un operador de supresión de errores, se utiliza para no escribir el manejo de errores, pero se basa en el comportamiento correcto de PHP en cuyo caso:
<?php echo @UNKNOWN_CONSTANT;
En este caso, aún se llamará al controlador de errores especificado en
set_error_handler()
, y el hecho de que se aplicó la supresión al error puede rastrearse llamando a la función
error_reporting()
dentro del controlador, en cuyo caso devolverá
0
.
Si suprime los errores de esta manera, esto reduce la carga en el procesador en comparación con si simplemente los oculta (consulte la prueba comparativa anterior), pero en cualquier caso, suprimir los errores es malo
TareaCompruebe cómo la supresión de errores con @
afecta el ejemplo de bucle anterior.
Excepciones
En la era de PHP4 no hubo excepciones, todo fue mucho más complicado, y los desarrolladores lucharon con los errores lo mejor que pudieron, fue una batalla no por la vida sino por la muerte ... Puedes sumergirte en esta fascinante historia de la confrontación en el artículo Código Excepcional. Parte 1 ¿Debería leerlo ahora? No puedo dar una respuesta definitiva, solo quiero señalar que esto lo ayudará a comprender la evolución del lenguaje y revelará todo el encanto de las excepciones.
Las excepciones son eventos excepcionales en PHP, a diferencia de los errores, no solo indican un problema, sino que requieren acciones adicionales por parte del programador para manejar cada caso específico.
Por ejemplo, una secuencia de comandos debe guardar algunos datos en un archivo de caché si algo salió mal (sin acceso de escritura, sin espacio en disco), se genera una excepción del tipo correspondiente y la decisión se toma en el controlador de excepciones: guardar en otra ubicación o informar al usuario sobre el problema.
Una excepción es un objeto de la clase
Exception
o de uno de sus muchos descendientes, contiene el texto del error, el estado y también puede contener un enlace a otra excepción que se convirtió en la causa raíz de esto. El modelo de excepción en PHP es similar al utilizado en otros lenguajes de programación. Se puede lanzar una excepción (como se dice, "lanzar") utilizando el operador de
throw
, y puede atrapar ("atrapar") la instrucción
catch
. El código que arroja la excepción debe estar rodeado por un bloque
try
para capturar la excepción. Cada bloque de
try
debe tener al menos una
catch
coincidente o
finally
bloque:
try {
En cuyo caso vale la pena usar excepciones:
- si dentro del marco de un método / función hay varias operaciones que pueden fallar
- si su marco o biblioteca declara su uso
Para ilustrar el primer escenario, tomamos un ejemplo ya expresado de una función para escribir datos en un archivo; muchos factores pueden evitarnos, pero para decirle al código anterior cuál fue exactamente el problema, debe crear y lanzar una excepción:
$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';
En consecuencia, atraparemos estas excepciones de esta manera:
try {
Este ejemplo muestra un escenario muy simple para manejar excepciones, cuando tenemos cualquier excepción manejada de una manera. Pero a menudo, varias excepciones requieren un enfoque diferente para el procesamiento, y luego debe usar códigos de excepción y establecer la jerarquía de excepciones en la aplicación:
Ahora, si usa estas excepciones, puede obtener el siguiente código:
try {
Es importante recordar que la excepción es principalmente un evento excepcional, en otras palabras, una excepción a la regla. No necesita usarlos para manejar errores obvios, por ejemplo, para validar la entrada del usuario (aunque esto no es tan simple). En este caso, el controlador de excepciones debe escribirse en el lugar donde podrá manejarlo. Por ejemplo, el controlador para las excepciones causadas por la inaccesibilidad de un archivo para escritura debe estar en el método responsable de seleccionar el archivo o el método que lo llama, para que pueda seleccionar otro archivo o un directorio diferente.
Entonces, ¿qué pasará si no atrapas la excepción? Obtendrá "Error grave: excepción no detectada ...". Desagradable
Para evitar esta situación, debe usar la función
set_exception_handler () y establecer el controlador para las excepciones que se lanzan fuera del bloque try-catch y que no se han procesado. Después de llamar a dicho controlador, la ejecución del script se detendrá:
También le contaré acerca de la construcción utilizando el bloque
finally
: este bloque se ejecutará independientemente de si se produjo una excepción o no:
try {
Para entender lo que esto nos da, daré el siguiente ejemplo de uso del bloque
finally
:
try {
Es decir recuerde: el
finally
bloque se ejecutará incluso si lanza una excepción arriba en el bloque
catch
(de hecho, esto es lo que pretendía).
Para un artículo introductorio de información correcto, quién anhela más detalles, los encontrará en el artículo
Código excepcional ;)
TareaEscriba su controlador de excepciones, con la salida del texto del archivo donde ocurrió el error, y todo esto con el resaltado de sintaxis, también no olvide mostrar la traza en una forma legible. Como referencia, mira lo genial que se ve en
whoops .
PHP7: todo no es como era antes
Entonces, ahora ha aprendido toda la información anterior y ahora lo cargaré con innovaciones en PHP7, es decir
Hablaré sobre lo que encontrará cuando trabaje en un proyecto PHP moderno. Anteriormente, te lo dije y mostré con ejemplos qué muleta necesitas construir para detectar errores críticos, y así, en PHP7 decidieron arreglarlo, ¿pero? como siempre? atado a la compatibilidad con versiones anteriores del código y recibido, aunque es una solución universal, pero está lejos de ser ideal. Y ahora sobre los puntos sobre los cambios:- cuando
E_ERROR
ocurren errores fatales del tipo o errores fatales con la posibilidad de procesar E_RECOVERABLE_ERROR
PHP arroja una excepción - estas excepciones no heredan la clase Exception (recuerde, hablé sobre compatibilidad con versiones anteriores, todo es por su bien)
- estas excepciones heredan la clase Error
- Ambas clases de excepción y error implementan la interfaz Throwable
- no puedes implementar la interfaz de lanzamiento en tu código
La interfaz Throwable
nos repite casi por completo Exception
: interface Throwable { public function getMessage(): string; public function getCode(): int; public function getFile(): string; public function getLine(): int; public function getTrace(): array; public function getTraceAsString(): string; public function getPrevious(): Throwable; public function __toString(): string; }
¿Es dificil? Ahora, por ejemplo, tome los que eran más altos y se modernizaron un poco: try {
Como resultado, detectamos el error e imprimimos: object(ParseError)
Como puede ver, detectaron la excepción ParseError , que es la sucesora de la excepción Error
que implementa la interfaz Throwable
en la casa que Jack construyó. Hay muchas otras excepciones, pero no atormentaré; para mayor claridad, daré una jerarquía de excepciones: interface Throwable |- Exception implements Throwable | |- ErrorException extends Exception | |- ... extends Exception | `- ... extends Exception `- Error implements Throwable |- TypeError extends Error |- ParseError extends Error |- ArithmeticError extends Error | `- DivisionByZeroError extends ArithmeticError `- AssertionError extends Error
Y un poco más de detalle:TypeError - para errores cuando el tipo de argumentos de función no coincide con el tipo pasado: try { (function(int $one, int $two) { return; })('one', 'two'); } catch (TypeError $e) { echo $e->getMessage(); }
ArithmeticError : puede ocurrir durante operaciones matemáticas, por ejemplo, cuando el resultado del cálculo excede el límite asignado para un entero: try { 1 << -1; } catch (ArithmeticError $e) { echo $e->getMessage(); }
DivisionByZeroError - error de división por cero: try { 1 / 0; } catch (ArithmeticError $e) { echo $e->getMessage(); }
AssertionError : una bestia rara que aparece cuando no se cumple la condición especificada en afirmar () : ini_set('zend.assertions', 1); ini_set('assert.exception', 1); try { assert(1 === 0); } catch (AssertionError $e) { echo $e->getMessage(); }
En la configuración de Directiva de producción-servidor zend.assertions
y assert.exception
cortado, y con razón
Encontrará una lista completa de excepciones predefinidas en el manual oficial , en la misma jerarquía de excepciones SPL .TareaEscriba un controlador de errores universal para PHP7 que detecte todas las excepciones posibles.
Al escribir esta sección, se utilizaron materiales del artículo Excepciones y errores de lanzamiento en PHP 7 .
Uniformidad
- Hay errores, excepciones, pero ¿puede todo esto ser de alguna manera acumulado?Sí, es fácil, tenemos set_error_handler()
uno y nadie nos prohibirá lanzar una excepción dentro de este controlador:
Pero este enfoque con PHP7 es redundante, ahora puede manejar todo Throwable
: try { } catch (\Throwable $e) {
Depuración
A veces, para depurar código, debe realizar un seguimiento de lo que le sucedió a una variable u objeto en una etapa determinada, para estos fines hay una función debug_backtrace () y debug_print_backtrace () que devolverá el historial de llamadas a funciones / métodos en el orden inverso: <?php function example() { echo '<pre>'; debug_print_backtrace(); echo '</pre>'; } class ExampleClass { public static function method () { example(); } } ExampleClass::method();
Como resultado de la ejecución de la función debug_print_backtrace()
, se mostrará una lista de llamadas que nos llevaron a este punto:
Puede verificar el código en busca de errores de sintaxis utilizando la función php_check_syntax () o el comando php -l [ ]
, pero no he visto su uso.Afirmar
También me gustaría hablar sobre una bestia tan exótica como afirmar () en PHP. En realidad, esta pieza se puede considerar como una imitación de la metodología de programación por contrato, y luego te diré cómo nunca la usé :)La función assert()
cambió su comportamiento durante la transición de la versión 5.6 a la 7.0, y todo cambió aún más fuerte en la versión 7.2, así que lea cuidadosamente los registros de cambios y PHP;)
El primer caso es cuando necesita escribir TODO directamente en el código, para no olvidarse de implementar la funcionalidad dada:
Como resultado de ejecutar este código, obtenemos E_WARNING
: Warning: assert(): Remove it! failed
PHP7 se puede cambiar al modo de excepción, y en lugar de un error, siempre aparecerá una excepción AssertionError
:
Como resultado, esperamos una excepción AssertionError
.Si es necesario, puede lanzar una excepción arbitraria: assert(false, new Exception("Remove it!"));
Recomendaría usar etiquetas @TODO
, los IDE modernos funcionan bien con ellos, y no necesitará poner esfuerzo y recursos adicionales para trabajar con ellos, aunque la tentación de "anotar" con ellos es excelente
El segundo caso de uso es crear algún tipo de TDD, pero recuerde, esto es solo una similitud. Aunque, si te esfuerzas, puedes obtener un resultado divertido que te ayudará a probar tu código:
La tercera opción es un tipo de programación por contrato, cuando describió las reglas para usar su biblioteca, pero desea asegurarse de que se le entiende correctamente y, en ese caso, informar inmediatamente al desarrollador de un error (ni siquiera estoy seguro de haberlo entendido correctamente, pero un ejemplo El código funciona bastante): function setupDb ($settings) {
Si está interesado en contratos, específicamente para usted tengo un enlace al marco PhpDeal .
Nunca use assert()
para verificar los parámetros de entrada, porque de hecho assert()
interpreta el primer parámetro (se comporta como eval()
), y esto está plagado de inyección PHP. Y sí, este es el comportamiento correcto, porque si deshabilita la aserción, todos los argumentos pasados serán ignorados, y si hace como en el ejemplo anterior, el código se ejecutará y el resultado booleano de la ejecución se pasará dentro de la aserción deshabilitada. Ah, y cambió en PHP 7.2 :)
Si tienes una experiencia de uso en vivo assert()
, comparte conmigo, te lo agradeceré. Y sí, aquí hay otra lectura interesante para usted sobre este tema: Afirmaciones PHP , con la misma pregunta al final :)En conclusión
Escribiré las conclusiones de este artículo para usted:- Errores de lucha: no deben estar en su código
- Use excepciones: trabajar con ellos debe organizarse adecuadamente y habrá felicidad
- Afirmar: aprendí sobre ellos, y bueno
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.Gracias a Maxim Slesarenko por su ayuda en la redacción del artículo.