Fatal error uncaught error call to undefined method

I have created a separate class for database and users. Database.php class Database{ private $db; public function __construct(){ /*** mysql hostname ***/ $hostname = 'localhost'; ...

I have created a separate class for database and users.

Database.php

 class Database{

     private $db;


    public function __construct(){

  /*** mysql hostname ***/
$hostname = 'localhost';

/*** mysql username ***/
$username = 'username_web';

/*** mysql password ***/
$password = 'password_web';



try {
    $this->db = new PDO("mysql:host=$hostname;dbname=kamadhenu_web", $username, $password);
    /*** echo a message saying we have connected ***/

       }
catch(PDOException $e)
    {
    echo $e->getMessage();
    }

 }

  /*** Query Function ***/
 public function query($sql)
        {
        return $this->db->query($sql);
        }



 }

Users.php

class Users{

     private $db;

public function __construct($database) {
$this->db = $database;

}


     public function login($username, $password)
     {

        $query=$this->db->prepare("SELECT `password`, `id` FROM `users` WHERE `username` = ?");
        $query->bindValue(1, $username);
        try{
        $query->execute();
        $data = $query->fetch();
        $stored_password = $data['password'];
        $id = $data['id'];
        #hashing the supplied password and comparing it with the stored hashed password.
        if($stored_password === sha1($password)){
        return $id; 
        }else{
        return false;   
        }

        }catch(PDOException $e){
        die($e->getMessage());
}

 }




 }

Here is my Login page with username and password.

 login.php

include('database.php');
include('users.php');

$dbh= new Database();
$users= new Users($dbh);


if (isset($_POST['submit']))

{ 

$username= $_POST['username'];
$password= $_POST['password'];

    $login = $users->login($username, $password);




        if ($login === false) {
        $errors[] = 'Sorry, that username/password is invalid';
        }
        else {
        // username/password is correct and the login method of the $users object returns the user's id, which is stored in $login.

        $_SESSION['id'] = $login; // The user's id is now set into the user's session in the form of $_SESSION['id']
        #Redirect the user to home.php.
        header('Location: list-updates.php');
        exit();
        }





}

When I execute I get an error:

Call to undefined method Database::prepare()

I’m getting this error on the bottom of the suite result in any project I run with phpunit.

I tested with dev-master (5.4-g7735f76) and 5.2.12.

PHP Fatal error:  Uncaught Error: Call to undefined method PHPUnit_Framework_TestResult::warnings() in /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php:297
Stack trace:
#0 /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php(187): PHPUnit_TextUI_ResultPrinter->printWarnings(Object(PHPUnit_Framework_TestResult))
#1 /home/jean/projects/doctrine2/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(446): PHPUnit_TextUI_ResultPrinter->printResult(Object(PHPUnit_Framework_TestResult))
#2 /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php(162): PHPUnit_TextUI_TestRunner->doRun(Array, true)
#3 /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php(113): PHPUnit_TextUI_Command->run(Array, true)
#4 /home/jean/.composer/vendor/phpunit/phpunit/phpunit(47): PHPUnit_TextUI_Command::main()
#5 {main}
  thrown in /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php on line 297

Fatal error: Uncaught Error: Call to undefined method PHPUnit_Framework_TestResult::warnings() in /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php:297
Stack trace:
#0 /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php(187): PHPUnit_TextUI_ResultPrinter->printWarnings(Object(PHPUnit_Framework_TestResult))
#1 /home/jean/projects/doctrine2/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(446): PHPUnit_TextUI_ResultPrinter->printResult(Object(PHPUnit_Framework_TestResult))
#2 /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php(162): PHPUnit_TextUI_TestRunner->doRun(Array, true)
#3 /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php(113): PHPUnit_TextUI_Command->run(Array, true)
#4 /home/jean/.composer/vendor/phpunit/phpunit/phpunit(47): PHPUnit_TextUI_Command::main()
#5 {main}
  thrown in /home/jean/.composer/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php on line 297

Using the phpunit’s test suite I dont get this error. I wonder if it might be something related to my enviroment?

Good_Smile

0 / 0 / 0

Регистрация: 14.08.2016

Сообщений: 21

1

05.11.2016, 12:05. Показов 6900. Ответов 5

Метки нет (Все метки)


делаю angularjs приложение с php back-end. Оно должно показывать весь список товаров и добавлять товары методом POST.
Все работает (даже форма поиска), но когда заполняю форму и отправляю ее ничего не происходит, в логах пишет:
Fatal error: Call to undefined method Product::createProduct() in create_product.php on line 21.

Файл create_product.php:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
//get database connection
include_once 'config/database.php';
$database = new Database();
$db = $database->getConnection();
 
//Instantiate product object
include_once 'objects/product.php';
$product = new Product($db);
 
//get posted data
$data = json_decode(file_get_contents("php://input"));
 
//set product property values
$product->name = $data->name;
$product->price = $data->price;
$product->description = $data->description;
$product->created = date('Y-m-d H:i:s');
 
//create the product
if ($product->createProduct()) {
    echo 'product was created';
}
else {
    echo 'unable to create product';
}
?>

Файл product.php с классом product:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
class Product{
    // database connection and table name
    private $conn;
    private $table_name = "products";
 
    // object properties
    public $id;
    public $name;
    public $description;
    public $price;
    public $created;
 
    // constructor with $db as database connection
    public function __construct($db){
        $this->conn = $db;
    }
 
    // read products
function readAll(){
 
    // select all query
    $query = "SELECT
                id, name, description, price, created
            FROM
                " . $this->table_name . "
            ORDER BY
                id DESC";
 
    // prepare query statement
    $stmt = $this->conn->prepare( $query );
 
    // execute query
    $stmt->execute();
 
    return $stmt;
}
}
 
//create product
function createProduct(){
    //query to insert record
    $query = "INSERT INTO".$this->table_name."SET
                                                                                                name=:name, price=:price, description=:description, created=:created";
    //prepare query
    $stmt = $this->conn->prepare($query);
 
    //posted values
    $this->name = htmlspecialchars(strip_tags($this->name));
    $this->price = htmlspecialchars(strip_tags($this->price));
    $this->description = htmlspecialchars(strip_tags($this->description));
 
    //bind values
    $stmt->bindParam(":name", $this->name);
    $stmt->bindParam(":price", $this->price);
    $stmt->bindParam(":description", $this->description);
    $stmt->bindParam(":created", $this->created);
 
    //execute query
    if ($stmt->execute()) {
        return true;
    } else{
        echo "<pre>";
            print_r($stmt->errorInfo());
        echo "</pre";
        return false;
    }
}
?>

В чем может быть проблема?

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



Эксперт PHP

3802 / 3160 / 1326

Регистрация: 01.08.2012

Сообщений: 10,717

05.11.2016, 12:18

2

Кажется вы на 38-ой строке закрыли класс, соответственно функция не относится к этому классу.



1



0 / 0 / 0

Регистрация: 14.08.2016

Сообщений: 21

05.11.2016, 16:35

 [ТС]

3

Блин, точно! Прозевал этот момент что-то. Спасибо. Правда теперь он другую ошибку дает:

Array
(
[0] => 42000
[1] => 1064
[2] => You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘name=’фыв’, price=’11’, description=’фывфыв’, c’ at line 2
)

Версия MySQL: 5.7.16-0ubuntu0.16.04.1 — (Ubuntu).

Я даже не понимаю «at line 2» — это в каком файле, или это вторая строка запроса, который проходит на сервере( то, что в product.php строки 43-44)?



0



Эксперт PHP

3802 / 3160 / 1326

Регистрация: 01.08.2012

Сообщений: 10,717

05.11.2016, 17:28

4

Лучший ответ Сообщение было отмечено Good_Smile как решение

Решение

Good_Smile, видимо да, надо пробелы поставить после INTO и перед SET.



1



0 / 0 / 0

Регистрация: 14.08.2016

Сообщений: 21

06.11.2016, 00:12

 [ТС]

5

Спасибо огромное



0



Alexey74

0 / 0 / 0

Регистрация: 01.08.2015

Сообщений: 52

07.11.2016, 08:20

6

Проверка тест

Добавлено через 57 секунд

Цитата
Сообщение от Good_Smile
Посмотреть сообщение

делаю angularjs приложение с php back-end. Оно должно показывать весь список товаров и добавлять товары методом POST.
Все работает (даже форма поиска), но когда заполняю форму и отправляю ее ничего не происходит, в логах пишет:
Fatal error: Call to undefined method Product::createProduct() in create_product.php on line 21.

Файл create_product.php:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
//get database connection
include_once 'config/database.php';
$database = new Database();
$db = $database->getConnection();
 
//Instantiate product object
include_once 'objects/product.php';
$product = new Product($db);
 
//get posted data
$data = json_decode(file_get_contents("php://input"));
 
//set product property values
$product->name = $data->name;
$product->price = $data->price;
$product->description = $data->description;
$product->created = date('Y-m-d H:i:s');
 
//create the product
if ($product->createProduct()) {
    echo 'product was created';
}
else {
    echo 'unable to create product';
}
?>

Файл product.php с классом product:

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
class Product{
    // database connection and table name
    private $conn;
    private $table_name = "products";
 
    // object properties
    public $id;
    public $name;
    public $description;
    public $price;
    public $created;
 
    // constructor with $db as database connection
    public function __construct($db){
        $this->conn = $db;
    }
 
    // read products
function readAll(){
 
    // select all query
    $query = "SELECT
                id, name, description, price, created
            FROM
                " . $this->table_name . "
            ORDER BY
                id DESC";
 
    // prepare query statement
    $stmt = $this->conn->prepare( $query );
 
    // execute query
    $stmt->execute();
 
    return $stmt;
}
}
 
//create product
function createProduct(){
    //query to insert record
    $query = "INSERT INTO".$this->table_name."SET
                                                                                                name=:name, price=:price, description=:description, created=:created";
    //prepare query
    $stmt = $this->conn->prepare($query);
 
    //posted values
    $this->name = htmlspecialchars(strip_tags($this->name));
    $this->price = htmlspecialchars(strip_tags($this->price));
    $this->description = htmlspecialchars(strip_tags($this->description));
 
    //bind values
    $stmt->bindParam(":name", $this->name);
    $stmt->bindParam(":price", $this->price);
    $stmt->bindParam(":description", $this->description);
    $stmt->bindParam(":created", $this->created);
 
    //execute query
    if ($stmt->execute()) {
        return true;
    } else{
        echo "<pre>";
            print_r($stmt->errorInfo());
        echo "</pre";
        return false;
    }
}
?>

В чем может быть проблема?

Тест

Добавлено через 57 секунд

Цитата
Сообщение от Good_Smile
Посмотреть сообщение

Спасибо огромное

тест

Добавлено через 28 секунд
тест

Добавлено через 32 секунды

Цитата
Сообщение от Good_Smile
Посмотреть сообщение

Спасибо огромное

Да проетстируем

Добавлено через 22 секунды

Цитата
Сообщение от Good_Smile
Посмотреть сообщение

Спасибо огромное

тест



0



Рекурсивный акроним словосочетания «PHP: Hypertext Preprocessor»
Добро пожаловать на форум PHP программистов!

Главная
Документация
Новости
Форум

За последние 24 часа нас посетили 11467 программистов и 1168 роботов. Сейчас ищут 307 программистов …

При изменении из mysqli на PDO возникла ошибка

Тема в разделе «PHP для новичков», создана пользователем konigwolf, 5 май 2022.


  1. konigwolf

    konigwolf
    Новичок

    С нами с:
    2 май 2022
    Сообщения:
    11
    Симпатии:
    0

    Добрый день. Столкнулся с проблемой. Выдает ошибку Fatal error: Uncaught Error: Call to undefined method mysqli_stmt::fetchAll() in C:xampphtdocsdvizscriptproduct_script.php:6 Stack trace: #0 C:xampphtdocsdvizscriptproduct_script.php(11): product_date(Object(mysqli)) #1 C:xampphtdocsdvizauthcatalog.php(14): product_print(Object(mysqli)) #2 C:xampphtdocsdvizindex.php(21): include_once(‘C:\xampp\htdocs…’) #3 {main} thrown in C:xampphtdocsdvizscriptproduct_script.php on line 6
    Не могу понять то ли дело в использовании MYSQL и для правильной работы нужна другая платформа либо где то накасячил.
    Это код работающий на mysqli_connect:

    PHP:
    1. function product_date($con){
    2.     $product_date = «SELECT * FROM product»;  
    3.         $result = mysqli_fetch_all(mysqli_query($con,$product_date),MYSQLI_NUM);
    4.         return $result;
    5. }
    6. function product_print($con){
    7.     $product_print = product_date($con);      for($i=0;$i<count($product_print);$i++){          
    8.     echo «<form action=«« method=«post« class=«cont«>          
    9.               <div class=«preg«>                  
    10.                    <div class=«cot«>
    11.                    <p class=«foto«>Фото:&nbsp;».$product_print[$i][2].«</p>                  
    12.                   </div>
    13.               </div>
    14.               <div class=«preg«>
    15.                    <h3 class=«product_name«.$product_print[$i][2].«</h3>
    16.               </div>
    17.                <div class=«preg«>
    18.                    <p class=«product_num«>Артикль:&nbsp;».$product_print[$i][1].«</p>
    19.               </div>
    20.                 <div class=«preg«>
    21.                    <p class=«product_cost«><b>Цена:</b>&nbsp;».$product_print[$i][3].«</p>
    22.               </div>
    23.               <div class=«preg«>
    24.                    <p class=«product_value«>Осталось:&nbsp;».$product_print[$i][4].«</p>
    25.                   <input type=«hidden« name=«product_id« value=».$$product_print[$i][0].«>
    26.               </div>
    27.            
    28.               <div class=«preg«>                  
    29.                    <input type=«number« name=«value_product«>
    30.               </div>
    31.               <div class=«preg«>  
    32.                  
    33.               </div>
    34.               <div class=«preg«>
    35.                     <button type=«submit« name=«product_buy« value=«product_buy« class=«but«>Купить</button>
    36.               </div>              
    37.               </form>»;
    38.     }
    39. }

    Это код не работает на PDO;

    PHP:
    1. function product_date($con){
    2.     $product_date = «SELECT * FROM product»;
    3.     $prepare = $con->prepare($product_date);
    4.     $prepare->execute();
    5.     $result = $prepare->fetchAll(PDO::FETCH_NUM);
    6.     return $result;
    7. }
    8. function product_print($con){
    9.     $product_print = product_date($con);  
    10.     for($i=0;$i<count($product_print);$i++){      
    11.     echo «<form action=«« method=«post« class=«cont«>          
    12.               <div class=«preg«>                  
    13.                    <div class=«cot«>
    14.                    <p class=«foto«>Фото:&nbsp;».$product_print[$i][2].«</p>                  
    15.                   </div>
    16.               </div>
    17.               <div class=«preg«>
    18.                    <h3 class=«product_name«.$product_print[$i][2].«</h3>
    19.               </div>
    20.                <div class=«preg«>
    21.                    <p class=«product_num«>Артикль:&nbsp;».$product_print[$i][1].«</p>
    22.               </div>
    23.                 <div class=«preg«>
    24.                    <p class=«product_cost«><b>Цена:</b>&nbsp;».$product_print[$i][3].«</p>
    25.               </div>
    26.               <div class=«preg«>
    27.                    <p class=«product_value«>Осталось:&nbsp;».$product_print[$i][4].«</p>
    28.                   <input type=«hidden« name=«product_id« value=».$$product_print[$i][0].«>
    29.               </div>
    30.            
    31.               <div class=«preg«>                  
    32.                    <input type=«number« name=«value_product«>
    33.               </div>
    34.               <div class=«preg«>  
    35.                  
    36.               </div>
    37.               <div class=«preg«>
    38.                     <button type=«submit« name=«product_buy« value=«product_buy« class=«but«>Купить</button>
    39.               </div>              
    40.               </form>»;
    41.     }
    42. }

    При этом даже разбирал по функциям на отдельном файле что бы выдавало одно и тоже. Но при этом выходит ошибка. Подскажите пожалуйста может кто знает. Я новичок и могу где-то ошибаться.

    #1


    konigwolf,

    5 май 2022


  2. Sail

    Sail
    Старожил

    С нами с:
    1 ноя 2016
    Сообщения:
    1.524
    Симпатии:
    345

    $con должна быть объектом класса PHP: PDO — Manual, а не PHP: mysqli — Manual

    #2


    Sail,

    5 май 2022


  3. konigwolf

    konigwolf
    Новичок

    С нами с:
    2 май 2022
    Сообщения:
    11
    Симпатии:
    0

    Пардон. Запутался с копией. Проблема решена. Причина моя невнимательность.Спасибо большое.

    #3


    konigwolf,

    6 май 2022

(Вы должны войти или зарегистрироваться, чтобы разместить сообщение.)

Показать игнорируемое содержимое

Ваше имя или e-mail:
У Вас уже есть учётная запись?
  • Нет, зарегистрироваться сейчас.
  • Да, мой пароль:
  • Забыли пароль?

Запомнить меня

PHP 8.0 no longer allows to call non-static class methods with the static call operator (::).

Calling non-static methods statically raised a PHP deprecation notice in all PHP 7 versions, and raised a Strict Standards notice in PHP 5 versions.

class Foo {
    public function bar() {}
}
Foo::bar();

// Deprecated: Non-static method Foo::bar() should not be called statically in ... on line ...

In PHP 8.0 and later, this results in a fatal error:

class Foo {
    public function bar() {}
}
Foo::bar();

// Fatal error: Uncaught Error: Call to undefined method Foo::bar() in ...:...

Note that this only affects calling non-static methods statically. Although discouraged, calling a static method non-statically ($this->staticMethod()) is allowed.

This change is implemented throughout the engine.

Variable Functions

class Foo {
    public function bar() {}
}

['Foo', 'bar']();
// Fatal error: Uncaught Error: Non-static method Foo::bar() cannot be called statically in ...:...

Callables

PHP no longer considers an array with class name and a method (['Foo', 'bar']) as a valid callable, and results in a fatal error. This includes PHP core functions that expect a callable. If such callable is passed to a function that expects a valid callable, a TypeError will be thrown instead of a fatal error at call-time.

class Foo {
    public function bar() {}
}

call_user_func(['Foo', 'bar']);
call_user_func_array(['Foo', 'bar'], []);
// Fatal error: Uncaught TypeError: call_user_func(): Argument #1 ($function) must be a valid callback, non-static method Foo::bar() cannot be called statically in ...:...

This affects all functions ranging from call_user_func and call_user_func_array to register_shutdown_function, set_error_handler, set_error_handler.

register_shutdown_function function in PHP 8.0 versions until beta3 raised a PHP warning at the time register_shutdown_function function is called instead of the current behavior of throwing a TypeError exception. This was corrected in PHP beta4.

is_callable

is_callable function returns false on callable that calls non-static methods statically. It returned true prior to PHP 8.0.

class Foo {
    public function bar() {}
}

is_callable(['Foo', 'bar']); // false

static, self, and parent

static, self, and and parent pointers can continue to use the static call syntax inside a class.

class Test extends UpperTest{
    public function foo(): {}
    public function bar() {
        static::foo();
        self::foo();
        parent::foo();
    }
}

The call above is still allowed because static, self, and parent are used inside the class scope.

static:: and self:: calls are identical to $this-> calls on non-static methods, and improves readability. In the example above, static::foo() and self::foo() calls can be safely replaced with $this->foo() to improve readability because foo is not a static method.

Backwards Compatibility Impact

For existing code that get fatal errors in PHP 8.0, the fix can as simple as using the correct syntax if there is a class instance in the same scope.

class Foo {
    public function bar() {}
}

$foo = new Foo();
- Foo::bar();
+ $foo->bar(); 

If there is no instantiated class object, and if the class can be instantiated without any parameters or side effects, it will be simple replacement as well.

class Foo {
    public function bar() {}
}

- Foo::bar();
+ (new Foo())->bar(); 

If the class constructor requires parameters, or tends to make any state changes, the fix can be more complicated. The instance needs to be injected to the scope the static call is made.

Note that functions that expect a callable parameter no longer accept callables with a non-static method as static method. A TypeError exception will be thrown when the callable is passed, as opposed to when the callable is invoked.

class Foo {
    public function bar() {}
}
function takeCallable(callable $func) {}

- takeCallable(['Foo', 'bar']);
+ takeCallable([new Foo(), 'bar']);

is_callable function no longer returns true for such callables either.


Implementation

composer require fails with Composer 2

Steps to reproduce

comp require —update-no-dev —update-with-dependencies —no-plugins drupal/entity drupal/smtp drupal/spambot drupal/webform:^6.0 wikimedia/composer-merge-plugin

> DrupalCoreComposerComposer::vendorTestCodeCleanup

Fatal error: Uncaught Error: Call to undefined method ComposerDependencyResolverOperationUpdateOperation::getJobType() in /home/stelnews/public_html/core/lib/Drupal/Core/Composer/Composer.php:170
Stack trace:
#0 phar:///home/stelnews/public_html/composer/src/Composer/EventDispatcher/EventDispatcher.php(319): DrupalCoreComposerComposer::vendorTestCodeCleanup(Object(ComposerInstallerPackageEvent))
#1 phar:///home/stelnews/public_html/composer/src/Composer/EventDispatcher/EventDispatcher.php(216): ComposerEventDispatcherEventDispatcher->executeEventPhpScript('Drupal\Core\Com...', 'vendorTestCodeC...', Object(ComposerInstallerPackageEvent))
#2 phar:///home/stelnews/public_html/composer/src/Composer/EventDispatcher/EventDispatcher.php(123): ComposerEventDispatcherEventDispatcher->doDispatch(Object(ComposerInstallerPackageEvent))
#3 phar:///home/stelnews/public_html/composer/src/Composer/Installer/InstallationManager.php(386): ComposerEventDispatcherEventDispatcher->dispatchPackageEvent('post-package-up...', in /home/stelnews/public_html/core/lib/Drupal/Core/Composer/Composer.php on line 170

Proposed resolution

Downgrade to composer 1. But that fails with same error!

Remaining tasks

Fix composer2

User interface changes

API changes

Data model changes

Release notes snippet

An incompatibility with Composer 2, which affects a minority of sites when trying to update certain packages, has been fixed in Drupal 9.2.N, 9.3.N, and later. This incompatibility may cause fatal errors similar to:

> DrupalCoreComposerComposer::vendorTestCodeCleanup

Fatal error: Uncaught Error: Call to undefined method ComposerDependencyResolverOperationUpdateOperation::getJobType()

This problem can be fixed permanently by running these commands on any version of Drupal 9:

composer config --unset scripts.post-package-install
composer config --unset scripts.post-package-update
composer require drupal/core-vendor-hardening:^9

Other workarounds require updating Drupal to 9.2.N, 9.3.N, or later, and are documented at https://www.drupal.org/node/3267857.

PHP для начинающих. Обработка ошибок +32

PHP


Рекомендация: подборка платных и бесплатных курсов Java — https://katalog-kursov.ru/

image

Не совершает ошибок только тот, кто ничего не делает, и мы тому пример — сидим и трудимся не покладая рук, читаем Хабр :)

В этой статье я поведу свой рассказа об ошибках в PHP, и о том как их обуздать.

Ошибки

Разновидности в семействе ошибок

Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.

Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

Фатальные ошибки

Самый грозный вид ошибок — фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.

E_PARSE

Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:

<?php
/**
 * Parse error: syntax error, unexpected end of file
 */
{

Или написали на непонятном языке:

<?php
/**
 * Parse error: syntax error, unexpected '...' (T_STRING)
 */
Тут будет ошибка парсера

Лишние скобочки тоже встречаются, и не так важно круглые либо фигурные:

<?php
/**
 * Parse error: syntax error, unexpected '}'
 */
}

Отмечу один важный момент — код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:

<?php
// этот код не сработает
error_reporting(E_ALL);
ini_set('display_errors', 1);

// т.к. вот тут
ошибка парсера

E_ERROR

Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин. Эта ошибка так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:

Не был найден подключаемый файл:

/**
 * Fatal error: require_once(): Failed opening required 'not-exists.php' 
 * (include_path='.:/usr/share/php:/usr/share/pear')
 */
require_once 'not-exists.php';

Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:

/**
 * Fatal error: Uncaught exception 'Exception'
 */
throw new Exception();

При попытке вызвать несуществующий метод класса:

/**
 * Fatal error: Call to undefined method stdClass::notExists()
 */
$stdClass = new stdClass();
$stdClass->notExists();

Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:

/**
 * Fatal Error: Allowed Memory Size
 */
$arr = array();

while (true) {
    $arr[] = str_pad(' ', 1024);
}

Очень часто встречается при чтении либо загрузки больших файлов, так что будьте внимательны с вопросом потребляемой памяти

Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug (да, данная ошибка может проявиться в таком виде только при включении xdebug расширения):

/**
 * Fatal error: Maximum function nesting level of '256' reached, aborting!
 */
function deep() {
    deep();
}
deep();

Не фатальные

Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик. Именно такие ошибки доставляют больше всего хлопот начинающим разработчикам.

E_WARNING

Частенько встречается, когда подключаешь файл с использованием include, а его не оказывается на сервере или вы ошиблись указывая путь к файлу:

/**
 * Warning: include_once(): Failed opening 'not-exists.php' for inclusion
 */
include_once 'not-exists.php';

Бывает, если используешь неправильный тип аргументов при вызове функций:

/**
 * Warning: join(): Invalid arguments passed
 */
join('string', 'string');

Их очень много, и перечислять все не имеет смысла…

E_NOTICE

Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.

Когда обращаются к неопределенной переменной:

/**
 * Notice: Undefined variable: a
 */
echo $a;

Когда обращаются к несуществующему элементу массива:

/**
 * Notice: Undefined index: a
 */
$b = [];
$b['a'];

Когда обращаются к несуществующей константе:

/**
 * Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT'
 */
echo UNKNOWN_CONSTANT;

Когда не конвертируют типы данных:

/**
 * Notice: Array to string conversion
 */
echo array();

Для избежания подобных ошибок — будьте внимательней, и если вам IDE подсказывает о чём-то — не игнорируйте её:

PHP E_NOTICE in PHPStorm

E_STRICT

Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывает. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this:

/**
 * Strict standards: Non-static method Strict::test() should not be called statically
 */
class Strict {
    public function test() {
        echo "Test";
    }
}

Strict::test();

Данный тип ошибок актуален для PHP версии 5.6, и практически все их выпилили из
7-ки. Почитать подробней можно в соответствующей RFC. Если кто знает где ещё остались данные ошибки, то напишите в комментариях

E_DEPRECATED

Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):

/**
 * Deprecated: Function split() is deprecated
 */
// данная функция, удалена из PHP 7.0
// считается устаревшей с PHP 5.3
split(',', 'a,b');

В моём редакторе подобные функции будут зачёркнуты:

PHP E_DEPRECATED in PHPStorm

Пользовательские

Этот вид, которые «разводит» сам разработчик кода, я уже давно их не встречал, и не рекомендую вам ими злоупотреблять:

  • E_USER_ERROR — критическая ошибка
  • E_USER_WARNING — не критическая ошибка
  • E_USER_NOTICE — сообщения которые не являются ошибками

Отдельно стоит отметить E_USER_DEPRECATED — этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():

/**
 * @deprecated Deprecated since version 1.2, to be removed in 2.0
 */
function generateToken() {
    trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED);
    // ...
    // code ...
    // ...
}

Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы display_errors:

  • если display_errors = on, то в случае ошибки браузер получит html c текстом ошибки и кодом 200
  • если же display_errors = off, то для фатальных ошибок код ответа будет 500 и результат не будет возвращён пользователю, для остальных ошибок — код будет работать неправильно, но никому об этом не расскажет

Приручение

Для работы с ошибками в PHP существует 3 функции:

  • set_error_handler() — устанавливает обработчик для ошибок, которые не обрывают работу скрипта (т.е. для не фатальных ошибок)
  • error_get_last() — получает информацию о последней ошибке
  • register_shutdown_function() — регистрирует обработчик который будет запущен при завершении работы скрипта. Данная функция не относится непосредственно к обработчикам ошибок, но зачастую используется именно для этого

Теперь немного подробностей об обработке ошибок с использованием set_error_handler(), в качестве аргументов данная функция принимает имя функции, на которую будет возложена миссия по обработке ошибок и типы ошибок которые будут отслеживаться. Обработчиком ошибок может так же быть методом класса, или анонимной функцией, главное, чтобы он принимал следующий список аргументов:

  • $errno — первый аргумент содержит тип ошибки в виде целого числа
  • $errstr — второй аргумент содержит сообщение об ошибке
  • $errfile — необязательный третий аргумент содержит имя файла, в котором произошла ошибка
  • $errline — необязательный четвертый аргумент содержит номер строки, в которой произошла ошибка
  • $errcontext — необязательный пятый аргумент содержит массив всех переменных, существующих в области видимости, где произошла ошибка

В случае если обработчик вернул true, то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:

<?php
// включаем отображение всех ошибок, кроме E_NOTICE
error_reporting(E_ALL & ~E_NOTICE);
ini_set('display_errors', 1);

// наш обработчик ошибок
function myHandler($level, $message, $file, $line, $context) {
    // в зависимости от типа ошибки формируем заголовок сообщения
    switch ($level) {
        case E_WARNING:
            $type = 'Warning';
            break;
        case E_NOTICE:
            $type = 'Notice';
            break;
        default;
            // это не E_WARNING и не E_NOTICE
            // значит мы прекращаем обработку ошибки
            // далее обработка ложится на сам PHP
            return false;
    }
    // выводим текст ошибки
    echo "<h2>$type: $message</h2>";
    echo "<p><strong>File</strong>: $file:$line</p>";
    echo "<p><strong>Context</strong>: $". join(', $', 
    array_keys($context))."</p>";
    // сообщаем, что мы обработали ошибку, и дальнейшая обработка не требуется
    return true;
}

// регистрируем наш обработчик, он будет срабатывать на для всех типов ошибок
set_error_handler('myHandler', E_ALL);

У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет — пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём

С обработчиком, который написан выше есть одна существенная проблема — он не ловит фатальные ошибки, и при таких ошибках вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:

function shutdown() {
    echo 'Этот текст будет всегда отображаться';
}
register_shutdown_function('shutdown');

Данная функция будет срабатывать всегда!

Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль «последних»:

function shutdown() {
    $error = error_get_last();
    if (
        // если в коде была допущена ошибка
        is_array($error) &&
        // и это одна из фатальных ошибок
        in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])
     ) {
        // очищаем буфер вывода (о нём мы ещё поговорим в последующих статьях)
        while (ob_get_level()) {
            ob_end_clean();
        }
        // выводим описание проблемы
        echo "Сервер находится на техническом обслуживании, зайдите позже";
    }
}
register_shutdown_function('shutdown');

Хотел обратить внимание, что данный код хоть ещё и встречается для обработки ошибок, и вы возможно вы даже с ним столкнётесь, но он потерял актуальность начиная с 7-ой версии PHP. Что пришло на замену я расскажу чуть погодя.

Задание

Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.

О прожорливости

Проведём простой тест, и выясним — сколько драгоценных ресурсов кушает самая тривиальная ошибка:

/**
 * Этот код не вызывает ошибок
 */

// засекаем время выполнения скрипта
$time= microtime(true);

define('AAA', 'AAA');
$arr = [];
for ($i = 0; $i < 10000; $i++) {
    $arr[AAA] = $i;
}

printf('%f seconds <br/>', microtime(true) - $time);

В результате запуска данного скрипта у меня получился вот такой результат:

0.002867 seconds

Теперь добавим ошибку в цикле:

/**
 * Этот код содержит ошибку
 */

// засекаем время выполнения скрипта
$time= microtime(true);

$arr = [];
for ($i = 0; $i < 10000; $i++) {
    $arr[BBB] = $i; // тут используем константанту, которая у нас не объявлена
}

printf('%f seconds <br/>', microtime(true) - $time);

Результат ожидаемо хуже, и на порядок (даже на два порядка!):

0.263645 seconds

Вывод однозначен — ошибки в коде приводят к лишней прожорливости скриптов — так что во время разработки и тестирования приложения включайте отображение всех ошибок!

Тестирование проводил на различных версиях PHP и везде разница в десятки раз, так что пусть это будет ещё одним поводом для исправления всех ошибок в коде

Где собака зарыта

В PHP есть спец символ «@» — оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:

<?php
echo @UNKNOWN_CONSTANT;

При этом обработчик ошибок указанный в set_error_handler() всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting() внутри обработчика, в этом случае она вернёт 0.

Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло

Задание

Проверьте, как влияет подавление ошибки с помощью @ на предыдущий пример с циклом.

Исключения

В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Не могу дать однозначный ответ, лишь хочу заметить, что это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений.

Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.

К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение — сохранить в другое место или сообщить пользователю о проблеме.

Исключение — это объект класса Exception либо одного из многих его наследников, содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, «бросить») при помощи оператора throw, и можно перехватить («поймать») оператором catch. Код генерирующий исключение, должен быть окружен блоком try, для того чтобы можно было перехватить исключение. Каждый блок try должен иметь как минимум один соответствующий ему блок catch или finally:

try {
    // код который может выбросить исключение
    if (random_int(0, 1)) {
        throw new Exception("One");
    }
    echo "Zero"
} catch (Exception $e) {
    // код который может обработать исключение
    echo $e->getMessage();
}

В каких случаях стоит применять исключения:

  • если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
  • если используемый вами фреймворк или библиотека декларируют их использование

Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл — помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:

$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';

// директории может не быть
if (!is_dir($directory)) {
    throw new Exception('Directory `logs` is not exists');
}

// может не быть прав на запись в директорию
if (!is_writable($directory)) {
    throw new Exception('Directory `logs` is not writable');
}

// возможно кто-то уже создал файл, и закрыл к нему доступ
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
    throw new Exception('System can't create log file');
}

fputs($file, date("[H:i:s]") . " donen");
fclose($file);

Соответственно ловить данные исключения будем примерно так:

try {
    // код который пишет в файл
    // ...
} catch (Exception $e) {
    // выводим текст ошибки
    echo "Не получилось: ". $e->getMessage();
}

В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую, различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:

// исключения файловой системы
class FileSystemException extends Exception {}

// исключения связанные с директориями
class DirectoryException extends FileSystemException {
    // коды исключений
    const DIRECTORY_NOT_EXISTS =  1;
    const DIRECTORY_NOT_WRITABLE = 2;
}

// исключения связанные с файлами
class FileException extends FileSystemException {}

Теперь, если использовать эти исключения то можно получить следующий код:

try {
    // код который пишет в файл
    if (!is_dir($directory)) {
        throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS);
    }

    if (!is_writable($directory)) {
        throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE);
    }

    if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
        throw new FileException('System can't open log file');
    }

    fputs($file, date("[H:i:s]") . " donen");
    fclose($file);
} catch (DirectoryException $e) {
    echo "С директорией возникла проблема: ". $e->getMessage();
} catch (FileException $e) {
    echo "С файлом возникла проблема: ". $e->getMessage();
} catch (FileSystemException $e) {
    echo "Ошибка файловой системы: ". $e->getMessage();
} catch (Exception $e) {
    echo "Ошибка сервера: ". $e->getMessage();
}

Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.

Так, а что будет если не поймать исключение? Вы получите «Fatal Error: Uncaught exception …». Неприятно.

Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:

// в качестве обработчика событий
// будем использовать анонимную функцию
set_exception_handler(function($exception) {
    /** @var Exception $exception */
    echo $exception->getMessage(), "<br/>n";
    echo $exception->getFile(), ':', $exception->getLine(), "<br/>n";
    echo $exception->getTraceAsString(), "<br/>n";
});

Ещё расскажу про конструкцию с использованием блока finally — этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:

try {
    // код который может выбросить исключение
} catch (Exception $e) {
    // код который может обработать исключение
    // если конечно оно появится
} finally {
    // код, который будет выполнен при любом раскладе
}

Для понимания того, что это нам даёт приведу следующий пример использования блока finally:

try {
    // где-то глубоко внутри кода
    // соединение с базой данных
    $handler = mysqli_connect('localhost', 'root', '', 'test');

    try {
        // при работе с БД возникла исключительная ситуация
        // ...
        throw new Exception('DB error');
    } catch (Exception $e) {
        // исключение поймали, обработали на своём уровне
        // и должны его пробросить вверх, для дальнейшей обработки
        throw new Exception('Catch exception', 0, $e);
    } finally {
        // но, соединение с БД необходимо закрыть
        // будем делать это в блоке finally
        mysqli_close($handler);
    }

    // этот код не будет выполнен, если произойдёт исключение в коде выше
    echo "Ok";
} catch (Exception $e) {
    // ловим исключение, и выводим текст
    echo $e->getMessage();
    echo "<br/>";
    // выводим информацию о первоначальном исключении
    echo $e->getPrevious()->getMessage();
}

Т.е. запомните — блок finally будет выполнен даже в том случае, если вы в блоке catch пробрасываете исключение выше (собственно именно так он и задумывался).

Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код ;)

Задание

Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира — посмотрите как это круто выглядит у whoops.

PHP7 — всё не так, как было раньше

Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы будете сталкиваться работая над современным PHP проектом. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот — в PHP7 это решили исправить, но? как обычно? завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:

  1. при возникновении фатальных ошибок типа E_ERROR или фатальных ошибок с возможностью обработки E_RECOVERABLE_ERROR PHP выбрасывает исключение
  2. эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
  3. эти исключения наследуют класс Error
  4. оба класса Exception и Error реализуют интерфейс Throwable
  5. вы не можете реализовать интерфейс Throwable в своём коде

Интерфейс Throwable практически полностью повторяет нам 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;
}

Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:

try {
    // файл, который вызывает ошибку парсера
    include 'e_parse_include.php';
} catch (Error $e) {
    var_dump($e);
}

В результате ошибку поймаем и выведем:

object(ParseError)#1 (7) {
    ["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)"
    ["string":"Error":private] => string(0) ""
    ["code":protected] => int(0)
    ["file":protected] => string(49) "/www/education/error/e_parse_include.php"
    ["line":protected] => int(4)
    ["trace":"Error":private] => array(0) { }
    ["previous":"Error":private] => NULL
}

Как видите — поймали исключение ParseError, которое является наследником исключения Error, который реализует интерфейс Throwable, в доме который построил Джек. Ещё есть множество других исключений, но не буду мучать — для наглядности приведу иерархию исключений:

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

И чуть-чуть деталей:

TypeError — для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:

try {
    (function(int $one, int $two) {
        return;
    })('one', 'two');
} catch (TypeError $e) {
    echo $e->getMessage();
}

ArithmeticError — могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:

try {
    1 << -1;
} catch (ArithmeticError $e) {
    echo $e->getMessage();
}

DivisionByZeroError — ошибка деления на ноль:

try {
    1 / 0;
} catch (ArithmeticError $e) {
    echo $e->getMessage();
}

AssertionError — редкий зверь, появляется когда условие заданное в assert() не выполняется:

ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);

try {
    assert(1 === 0);
} catch (AssertionError $e) {
    echo $e->getMessage();
}

При настройках production-серверов, директивы zend.assertions и assert.exception отключают, и это правильно

Полный список предопределённых исключений вы найдёте в официальном мануале, там же иерархия SPL исключений.

Задание

Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.

При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7.

Единообразие

— Там ошибки, тут исключения, а можно это всё как-то до кучи сгрести?

Да запросто, у нас же есть set_error_handler() и никто нам не запретит внутри оного обработчика бросить исключение:

// Бросаем исключение вместо ошибок
function errorHandler($severity, $message, $file = null, $line = null)
{
    // Кроме случаев, когда мы подавляем ошибки с помощью @
    if (error_reporting() === 0) {
        return false;
    }
    throw new ErrorException($message, 0, $severity, $file, $line);
}

// Будем обрабатывать все-все ошибки
set_error_handler('errorHandler', E_ALL);

Но данный подход с PHP7 избыточен, со всем теперь справляется Throwable:

try {
    /** ... **/
} catch (Throwable $e) {
    // отображение любых ошибок и исключений
    echo $e->getMessage();
}

Отладка

Иногда, для отладки кода, нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:

<?php
function example() {
    echo '<pre>';
    debug_print_backtrace();
    echo '</pre>';
}

class ExampleClass {
    public static function method () {
        example();
    }
}

ExampleClass::method();

В результате выполнения функции debug_print_backtrace() будет выведен список вызовов приведших нас к данной точке:

#0  example() called at [/www/education/error/backtrace.php:10]
#1  ExampleClass::method() called at [/www/education/error/backtrace.php:14]

Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу], но я не встречал использования оных.

Assert

Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP. Собственно, этот кусочек можно рассматривать как мимикрию под контрактную методологию программирования, и дальше я расскажу вам как я никогда его не использовал :)

Функция assert() поменяла своё поведение при переходе от версии 5.6 к 7.0, и ещё сильней всё поменялось в версии 7.2, так что внимательней читайте changelog’и PHP ;)

Первый случай — это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:

// включаем asserts в php.ini
// zend.assertions=1
assert(false, "Remove it!");

В результате выполнения данного кода получим E_WARNING:

Warning: assert(): Remove it! failed

PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError:

// переключаем в режим «исключений»
ini_set('assert.exception', 1);

assert(false, "Remove it!");

В результате ожидаемо получаем исключение AssertionError.

При необходимости, можно выбрасывать произвольное исключение:

assert(false, new Exception("Remove it!"));

Я бы рекомендовал использовать метки @TODO, современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними, хотя с ними велик соблазн «забить»

Второй вариант использования — это создание некоего подобия TDD, но помните — это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:

// callback-функция для вывода информации в браузер
function backlog($script, $line, $code, $message) {
    echo $message;
}

// устанавливаем callback-функцию
assert_options(ASSERT_CALLBACK, 'backlog');

// отключаем вывод предупреждений
assert_options(ASSERT_WARNING,  false);

// пишем проверку и её описание
assert(sqr(4) === 16, 'When I send integer, function should return square of it');

// функция, которую проверяем
function sqr($a) {
    return; // она не работает
}

Третий вариант — некое подобие на контрактное программирование, когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):

/**
 * Настройки соединения должны передаваться в следующем виде
 *
 *     [
 *         'host' => 'localhost',
 *         'port' => 3306,
 *         'name' => 'dbname',
 *         'user' => 'root',
 *         'pass' => ''
 *     ]
 *
 * @param $settings
 */
function setupDb ($settings) {
    // проверяем настройки
    assert(isset($settings['host']), 'Db `host` is required');
    assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer');
    assert(isset($settings['name']), 'Db `name` is required, should be integer');

    // соединяем с БД
    // ...
}

setupDb(['host' => 'localhost']);

Если вас заинтересовали контракты, то специально для вас у меня есть ссылочка на фреймворк PhpDeal.

Никогда не используйте assert() для проверки входных параметров, ведь фактически assert() интерпретирует первый параметр (ведёт себя как eval()), а это чревато PHP-инъекцией. И да, это правильное поведение, ведь если отключить assert’ы, то все передаваемые аргументы будут проигнорированы, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения. А, и это поменяли в PHP 7.2 :)

Если у вас есть живой опыт использования assert() — поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме — PHP Assertions, с таким же вопросом в конце :)

В заключение

Я за вас напишу выводы из данной статьи:

  • Ошибкам бой — их не должно быть в вашем коде
  • Используйте исключения — работу с ними нужно правильно организовать и будет счастье
  • Assert — узнали о них, и хорошо

P.S.

Это репост из серии статей «PHP для начинающих»:

  • Сессия
  • Подключение файлов
  • Обработка ошибок

Если у вас есть замечания по материалу статьи, или возможно по форме, то описывайте в комментариях суть, и мы сделаем данный материал ещё лучше.

Спасибо Максиму Слесаренко за помощь в написании статьи.

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Fatal error uncaught error call to undefined function mysql connect in
  • Fatal error uncaught error call to undefined function imagecreatefromjpeg in
  • Fatal error uncaught error call to undefined function imagecreate
  • Fatal error uncaught error call to undefined function ereg in
  • Fatal error uncaught error call to undefined function each

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии