Bahnhofstraße 22, 52134 Herzogenrath Poniedziałek - piątek: 8:00 - 16:30 kontakt@milnersoftware.de 0176 66798849

Aktualności · Porady · · 5 min czytania

„Call to undefined function mysql_connect()“ – przyczyna i rozwiązanie

Po aktualizacji PHP strona pokazuje „Call to undefined function mysql_connect()“? Jak przepisać stary kod na mysqli lub PDO – bezpiecznie, z prepared statements.

Kleines Team arbeitet gemeinsam an Laptops an einem Holztisch

W skrócie: Funkcja mysql_connect() i wszystkie inne funkcje mysql_* zostały usunięte w PHP 7.0. Jeśli serwer działa na PHP 7 lub 8, tych funkcji po prostu nie ma – stąd błąd. Powrót do PHP 5 nie jest rozwiązaniem. Kod trzeba przepisać na mysqli albo PDO, najlepiej od razu z prepared statements.

Błąd pojawia się zwykle bez ostrzeżenia. Hosting zmienił wersję PHP i strona albo wewnętrzne narzędzie, które działało latami, pokazuje nagle białą stronę lub „500 Internal Server Error“. W logu błędów widać wtedy:

PHP Fatal error: Uncaught Error: Call to undefined function mysql_connect()
in /var/www/html/includes/db.php:5

Dotyczy to prawie zawsze kodu napisanego wiele lat temu: własnych portali dla klientów, starszych sklepów, formularzy rezerwacji czy programów do obsługi firmy.

Dlaczego mysql_connect() już nie istnieje?

Stare rozszerzenie MySQL pochodzi z początków PHP. Nie obsługuje prepared statements, programowania obiektowego ani wielu nowszych funkcji MySQL. Od PHP 5.5 było oznaczone jako przestarzałe, a w PHP 7.0 całkowicie usunięte. Od tego czasu są dwa następcy, oba wbudowane w PHP:

  • mysqli – przeznaczone dla MySQL i MariaDB, do użycia proceduralnie lub obiektowo.
  • PDO – jednolity interfejs dla różnych baz danych, z czytelną obsługą błędów.

Oba działają dobrze. Przy małych przeróbkach często wybieram mysqli, bo struktura bardziej przypomina stary kod. Przy projektach, które mają być rozwijane dłużej, wolę PDO.

Przed: typowy stary kod

$link = mysql_connect('localhost', 'benutzer', 'passwort');
mysql_select_db('shop', $link);

$id = $_GET['id'];
$result = mysql_query("SELECT name, preis FROM produkte WHERE id = $id");

while ($row = mysql_fetch_assoc($result)) {
    echo $row['name'];
}

Ten kod ma dwa problemy. Pierwszy jest oczywisty: nie działa pod PHP 7 i 8. Drugi jest groźniejszy i istniał od zawsze – o nim za chwilę.

Po: mysqli z prepared statement

$db = new mysqli('localhost', 'benutzer', 'passwort', 'shop');
$db->set_charset('utf8mb4');

$id = (int) ($_GET['id'] ?? 0);

$stmt = $db->prepare('SELECT name, preis FROM produkte WHERE id = ?');
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();

while ($row = $result->fetch_assoc()) {
    echo htmlspecialchars($row['name']);
}

Znak zapytania to symbol zastępczy. Wartość trafia do bazy osobno od zapytania SQL i nie może zmienić jego budowy. 'i' w bind_param oznacza liczbę całkowitą, 's' – tekst. get_result() wymaga sterownika mysqlnd, który na typowych hostingach jest zainstalowany.

Po: PDO

$pdo = new PDO(
    'mysql:host=localhost;dbname=shop;charset=utf8mb4',
    'benutzer',
    'passwort',
    [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]
);

$stmt = $pdo->prepare('SELECT name, preis FROM produkte WHERE id = :id');
$stmt->execute(['id' => (int) ($_GET['id'] ?? 0)]);

foreach ($stmt as $row) {
    echo htmlspecialchars($row['name']);
}

Nazwane symbole jak :id poprawiają czytelność dłuższych zapytań. Dzięki ERRMODE_EXCEPTION przy błędzie bazy kod zatrzymuje się z jasnym komunikatem, zamiast po cichu działać dalej.

Najważniejsze odpowiedniki

  • mysql_query() → $db->query() lub $db->prepare() / $pdo->prepare()
  • mysql_fetch_assoc() → $result->fetch_assoc() / $stmt->fetch()
  • mysql_num_rows() → $result->num_rows (w PDO lepiej przez COUNT(*))
  • mysql_insert_id() → $db->insert_id / $pdo->lastInsertId()
  • mysql_real_escape_string() → niepotrzebne, zamiast tego prepared statements
  • mysql_error() → wyjątki lub $db->error

Prawdziwe ryzyko: SQL injection w starym kodzie

Wróćmy do starego przykładu. Linia WHERE id = $id wstawia wartość z paska adresu do zapytania bez żadnej kontroli. Jeśli ktoś otworzy stronę z ?id=1 OR 1=1, baza zwróci wszystkie rekordy. Przy odrobinie wysiłku da się w ten sposób odczytać także inne tabele – np. dane klientów albo hashe haseł.

Wiele starych projektów używa mysql_real_escape_string() i dlatego uchodzi za „zabezpieczone“. To prawda tylko częściowo. Funkcja chroni jedynie wartości ujęte w cudzysłów. Przy liczbach bez cudzysłowów, jak w przykładzie wyżej, nie pomaga wcale. A wystarczy jedno zapomniane miejsce.

Dlatego odradzam samą podmianę nazw funkcji. Skoro i tak trzeba ruszyć kod, warto od razu wprowadzić prepared statements. Dodatkowa praca jest niewielka, a zysk dla bezpieczeństwa duży.

Jedno miejsce, gdzie prepared statements nie pomogą: nazwy tabel i kolumn, np. w ORDER BY. Nie da się ich przekazać jako symbolu zastępczego. Tu wartość sprawdza się z ustaloną listą dozwolonych kolumn.

Typowe błędy przy przeróbce

  • „Znajdź i zamień“ mysql_ na mysqli_: to nie działa. W wielu funkcjach mysqli połączenie jest pierwszym parametrem, czyli mysqli_query($link, $sql) zamiast mysql_query($sql, $link). Poza tym mysqli nie korzysta już automatycznie z „ostatnio otwartego połączenia“.
  • Zapomniane kodowanie znaków: bez set_charset('utf8mb4') lub charset=utf8mb4 polskie i niemieckie znaki zamieniają się w znaki zapytania albo „krzaki“.
  • Połączenie w funkcjach: stary kod często zakłada, że mysql_query() działa bez podania połączenia. W mysqli i PDO połączenie trzeba przekazać do każdej funkcji.
  • Warstwy zgodności na stałe: istnieją biblioteki, które odtwarzają stare funkcje mysql_* przy użyciu mysqli. Na kilka dni awaryjnie mogą pomóc. Luki bezpieczeństwa zostają jednak dokładnie takie same.
  • Test tylko strony głównej: zapytania do bazy są zwykle rozsiane po dziesiątkach plików. Proszę przetestować formularze, logowanie, wyszukiwarkę, eksport i panel administracyjny.

Jak to zrobić krok po kroku

  1. Pełna kopia zapasowa plików i bazy danych.
  2. Znalezienie wszystkich miejsc: grep -rn "mysql_" --include=*.php . pokaże każdą linię.
  3. Kopia testowa z nową wersją PHP.
  4. Jedno centralne połączenie z bazą (jeden plik, jeden obiekt), a potem przeróbka zapytań plik po pliku.
  5. Po każdym kroku sprawdzenie logu błędów i test wszystkich funkcji.
  6. Dopiero wtedy wgranie zmian na żywą stronę.

Kiedy potrzebna jest pomoc

Skrypt z pięcioma zapytaniami da się przerobić według tej instrukcji w jedno popołudnie. Inaczej wygląda to przy aplikacji rozbudowywanej latami, z setkami zapytań, rozproszoną logiką i bez dokumentacji. Do tego tam, gdzie jest jeszcze mysql_*, zwykle znajdą się też inne funkcje, których brakuje w nowszym PHP, np. each() albo create_function(), usunięte w PHP 8.0.

W takich przypadkach najpierw przeglądam kod i mówię Państwu, czy przeróbka ma sens, czy lepiej zbudować część od nowa. Jak wygląda ten proces, opisuję na stronie modernizacja starego systemu. Jeśli chodzi głównie o przeniesienie aplikacji na aktualną wersję PHP, przebieg i ceny znajdą Państwo na stronie aktualizacja PHP (od 690 €).

Autor: Przemysław Milner

Pozostałe wpisy

Przemysław Milner Porady

WordPress nie jest zły – tylko źle zbudowany

Wolny, dziurawy, ciągle się psuje? Zwykle winny nie jest WordPress, tylko page buildery, nadmiar wtyczek i motywy „multipurpose”. Co zmienia własny motyw.

Czytaj dalej

Przemysław Milner Porady

Zhakowany WordPress? Jak to rozpoznać

Obce strony w Google, przekierowania tylko na telefonie, nieznani administratorzy – sześć objawów, po których rozpoznają Państwo włamanie na WordPressa, i co zrobić od razu.

Czytaj dalej