„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.
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 przezCOUNT(*))mysql_insert_id()→$db->insert_id/$pdo->lastInsertId()mysql_real_escape_string()→ niepotrzebne, zamiast tego prepared statementsmysql_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)zamiastmysql_query($sql, $link). Poza tym mysqli nie korzysta już automatycznie z „ostatnio otwartego połączenia“. - Zapomniane kodowanie znaków: bez
set_charset('utf8mb4')lubcharset=utf8mb4polskie 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
- Pełna kopia zapasowa plików i bazy danych.
- Znalezienie wszystkich miejsc:
grep -rn "mysql_" --include=*.php .pokaże każdą linię. - Kopia testowa z nową wersją PHP.
- Jedno centralne połączenie z bazą (jeden plik, jeden obiekt), a potem przeróbka zapytań plik po pliku.
- Po każdym kroku sprawdzenie logu błędów i test wszystkich funkcji.
- 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 €).