PHP 7.4 has been around for a while and was the default and integrated PHP version in the widely used Ubuntu 20.04 (Focal Fossa). Although PHP 7.4 has been end of life since the end of 2022, security patches were still backported to the Ubuntu-maintained PHP packages.
As Ubuntu 20.04 will officially be end of life at the end of this month (April 2025), a lot of PHP 7.4 applications will need to be upgraded to a newer version. With an update from Ubuntu 20.04 to 22.04 the embedded PHP version will be upgraded to PHP 8.1. The currently latest Ubuntu LTS (24.04 "Noble") ships with PHP 8.3
My blog had also been running on PHP 7.4 for quite some time. When it was time to upgrade to a newer PHP version I went to PHP 8.1.
Luckily (or better said: out of experience) I have a dev/test site, where I can test my code on a newer PHP version. And no surprise there, there were some errors caused by the PHP upgrade. Here are a couple of them and how I fixed them.
Note: Later I upgraded PHP from 8.1 to 8.2 but no additional errors showed up. This means this article also applies to PHP 8.2 (for most situations anyway) and maybe even later versions.
The following error showed up:
Fatal error: Uncaught Error: Undefined constant "title" in /var/www/dev.example.com/index.php:75 Stack trace: #0 {main} thrown in /var/www/dev.example.com/index.php on line 75
In this situation this happens when data is retrieved with mysqli_query from a database using field/column names. In the previously used PHP version, the column name(s) could simply be mentioned inside the array:
$query = mysqli_query($dbh, "SELECT * FROM articles ORDER BY timeanddate DESC LIMIT 0,1");
while ($zeile = mysqli_fetch_assoc($query)) {
$title = $zeile[title];
// Do something
}
Now the table columns need to be defined as a 'string'. Otherwise PHP assumes the mentioned column (title in this situation) is a constant.
$query = mysqli_query($dbh, "SELECT * FROM articles ORDER BY timeanddate DESC LIMIT 0,1");
while ($zeile = mysqli_fetch_assoc($query)) {
$title = $zeile['title'];
// Do something
}
As an alternative, the mysqli_fetch_object remains the same as in older PHP versions:
$query = mysqli_query($dbh, "SELECT * FROM articles WHERE newsid=$EntryID");
$row = mysqli_fetch_object($query);
$iTitle = $row->title;
Up to the next error, where curly braces in PHP code seem to have lost the love. The following error showed up in the logs:
PHP Fatal error: Array and string offset access syntax with curly braces is no longer supported in /var/www/dev.example.com/3rdparty/xinha/contrib/php-xinha.php on line 468
The mentioned line shows the following code:
$last = strtolower($val{strlen($val)-1});
There are indeed curly braces in use: {strlen($val)-1}
According to this Stackoverflow answer, the curly braces can simply be replaced with square brackets. For example:
echo($str{0}); // OLD
echo($str[0]); // NEW
This means, the line leading to the PHP error was rewritten into this:
$last = strtolower($val[strlen($val)-1]);
And the error disappeared.
This error was actually fixed in the upstream project (Xinha) in the PHP 7.4 fixes commit and released in version 1.5.6. Updating Xinha to the newer release fixes this (and other errors related to older PHP code), too.
Still not done, another error eventually showed up. But this one was rather easy to solve, without any code change:
Fatal error: Uncaught Error: Class "Imagick" not found in /var/www/dev.example.com/3rdparty/xinha/plugins/MootoolsFileManager/mootools-filemanager/Assets/Connector/FileManager.php:2509 Stack trace: #0 /var/www/dev.example.com/3rdparty/xinha/plugins/MootoolsFileManager/mootools-filemanager/Assets/Connector/FileManager.php(1167): FileManager->onUpload()
As I switched to PHP 8.1, not all modules/packages were installed. The imagick module was simply forgotten.
root@lamp:~# apt-get install php8.1-imagick
root@lamp:~# systemctl restart php8.1-fpm
Error? Gone!
The next error caused me to scratch my head a bit more...
Fatal error: Uncaught mysqli_sql_exception: Unknown column 'preview' in 'where clause' in /var/www/dev.example.com/index.php:144 Stack trace: #0 /var/www/dev.example.com/index.php(144): mysqli_query() #1 {main} thrown in /var/www/dev.example.com/index.php on line 144
The mentioned line 144 shows the following PHP code:
// Load comments of this article
$commentsql = mysqli_query($dbh, "SELECT commentid FROM comments WHERE articleid=$articleid AND active=1 AND preview=0 ORDER BY dateandtime DESC");
This actually turned out to be a human (me) error on the old code. The "preview=0" condition was only supposed to be applied on the SQL query getting one or more articles, not on the comments.
In previous PHP version this did not cause a fatal error, but actually never returned anything either as that column (preview) doesn't even exist in the table (comments).
It's quite a helpful error!
And another error related to MySQL:
Fatal error: Uncaught mysqli_sql_exception: Field 'author' doesn't have a default value in /var/www/dev.example.com/cms/newblogentry.php:44 Stack trace: #0 /var/www/dev.example.com/cms/newblogentry.php(44): mysqli_query() #1 {main} thrown in /var/www/dev.example.com/cms/newblogentry.php on line 44
The relevant MySQL query wants to create a new row in the articles table:
$query="INSERT INTO articles(
title, description, content, timeanddate, location, tags, seourl)
VALUES ('$iTitle','$iDescription','$iContent','$iTimeanddate','$iLocation','$iTags','$iSeourl')";
// Show positive result or error message
if ($result=mysqli_query($dbh, $query)) {
echo "Blog entry \"$iTitle\" successfully added to database. <a href=\"index.php\">Back to administration.</a>"; }
else {
echo("Fehlermeldung=".mysqli_error($result)); }
mysqli_close($dbh);
}
Let's look at the table description:
MariaDB [claudiokuenzler]> describe articles;
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| newsid | int(4) | NO | PRI | NULL | auto_increment |
| title | varchar(150) | NO | | NULL | |
| description | varchar(300) | YES | | NULL | |
| content | mediumtext | NO | | NULL | |
| timeanddate | int(11) | YES | | NULL | |
| location | varchar(50) | NO | | NULL | |
| tags | varchar(200) | YES | | NULL | |
| seourl | varchar(500) | NO | | NULL | |
| author | varchar(100) | NO | | NULL | |
+----------------+--------------+------+-----+---------+----------------+
Of course there's some logic conflict: The author field is set to "NO" on Null (meaning the value cannot be NULL), yet the default value is NULL.
There are other fields with the same broken concept, but as the MySQL query above replaces the default value with an actual value, this doesn't cause a problem. The author value however is not set in the INSERT query, leading to the fatal error.
Instead of altering the table and changing the Default value of the author field, I decided to fix the INSERT query and add a value.
$anfrage="INSERT INTO articles(
title, description, content, timeanddate, location, tags, seourl, author)
VALUES ('$iTitle','$iDescription','$iContent','$iTimeanddate','$iLocation','$iTags','$iSeourl', 'Claudio Kuenzler')";
One of the principal admin pages I use is the "Create new blog entry" page, which obviously adds new blog posts into the database.
Although the PHP code told me that the article was successfully added to the database (after submitting the input form), there was an error logged:
Fatal error: Uncaught Error: mysqli object is already closed in /var/www/dev.example.com/cms/newblogentry.php:85 Stack trace: #0 /var/www/dev.example.com/cms/newblogentry.php(85): mysqli_close() #1 {main} thrown in /var/www/dev.example.com/cms/newblogentry.php on line 85
This error is caused by the same script and the same parts of it (INSERT query) as before. The relevant mysqli_query, which executes the INSERT statement, is:
// Show positive result or error message
if ($result=mysqli_query($dbh, $query)) {
echo "Blog entry \"$iTitle\" successfully added to database. <a href=\"index.php\">Back to administration.</a>"; }
else {
echo("Error=".mysqli_error($result)); }
mysqli_close($dbh);
}
The intriguing part is that the successful message "Blog entry... successfully added to database" was shown in the output (the insert into the database actually worked).
Only in case of a failure (in the else statement above) the MySQL connection would be closed using mysqli_close.
This would assume that "something" closed the MySQL connection before, very much at the end of the script on line 85, another mysqli_close should do that job.
84 <?php }
85 mysqli_close($dbh);
86 ?>
But looking closer at the else condition showed that the mysql_close is actually outside the else statement! By formatting this a bit differently, it can be seen better:
else { echo("Error=".mysqli_error($result)); }
To fix this, I fixed the closing else bracket and re-formatted the code to be optimized for the human eye:
if (isset($iSubmit)) {
[...]
$anfrage="INSERT INTO articles(
title, description, content, timeanddate, location, tags, seourl, author, authorurl)
VALUES ('$iTitle','$iDescription','$iContent','$iTimeanddate','$iLocation','$iTags','$iSeourl', 'Claudio Kuenzler', '/about/')";
// Show positive result or error message
if ($ergebnis=mysqli_query($dbh, $anfrage)) {
echo "Blog entry \"$iTitle\" successfully added to database. <a href=\"index.php\">Back to administration.</a>";
} else {
echo("Fehlermeldung=".mysqli_error($ergebnis));
mysqli_close($dbh);
}
}
And the error is gone.
It turned out that, due to improper code styling (and therefore creating an actual error in the code), the mysqli_close function was indeed launched twice in the same script. In the previous PHP version this did not cause a fatal error, now it does.
An additional and rather cryptic error logged was the following one found in the Apache error logs:
[proxy_fcgi:error] [pid 3883918:tid 3883918] [client 192.168.12.41:57920] AH01071: Got error 'e $commenttext in /var/www/dev.example.com/cms/comments.php on line 50', referer: https://dev.example.com/cms/comments.php?action=delall
The relevant PHP code shows the output of comments in a while loop:
while ($row = mysqli_fetch_assoc($query)) {
$name = $row['name'];
$email = $row['email'];
$commentid = $row['commentid'];
$newsid = $row['newsid'];
$active = $row['active'];
//$commenttext = substr("$row[text]", 0, 100);
$date = date("d.m.Y", $row['dateandtime']);
echo "
<tr>
<td width='75'>$date</td>
<td width='90'>$name</td>
<td width='25'><a href='$Path?action=acde&comment=$commentid'>$active</a></td>
<td width='25'><a href='$Path?action=del&comment=$commentid'>delete</a></td>
</tr>
<tr><td></td><td></td><td class='footer'>$commenttext</td><td></td><td></td></tr>
<tr><td><hr></td></tr>
";
}
The mentioned line 50 in the error is the echo of the $commenttext variable.
$commenttext is an unreferenced variable (commented above inside the while loop) and causes an error. Removed the table row from the echo output to fix the error, as commenttext is not needed here anyway.
No comments yet.
AWS Android Ansible Apache Apple Atlassian BSD Backup Bash Bluecoat CMS Chef Cloud Coding Consul Containers CouchDB DB DNS Database Databases Docker ELK Elasticsearch Filebeat FreeBSD Galera Git GlusterFS Grafana Graphics HAProxy HTML Hacks Hardware Icinga Influx Internet Java KVM Kibana Kodi Kubernetes LVM LXC Linux Logstash Mac Macintosh Mail MariaDB Minio MongoDB Monitoring Multimedia MySQL NFS Nagios Network Nginx OSSEC OTRS Observability Office OpenSearch PGSQL PHP Perl Personal PostgreSQL Postgres PowerDNS Proxmox Proxy Python Rancher Rant Redis Roundcube SSL Samba Seafile Security Shell SmartOS Solaris Surveillance Systemd TLS Tomcat Ubuntu Unix VMWare VMware Varnish Virtualization Windows Wireless Wordpress Wyse ZFS Zoneminder