Fixing code errors after PHP upgrade 7.4 -> 8.1 or 8.2

Written by - 0 comments

Published on - last updated on April 11th 2025 - Listed in Personal Coding PHP


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.

PHP

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.

Fatal error: Undefined constant

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;

Fatal error: curly braces is no longer supported

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.

Fatal error: Class imagick not found

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!

Fatal error: Unknown column in WHERE clause

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!

Fatal error: Field doesn't have a default value

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')";

Fatal error: mysqli object is already closed

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.

Got error: 'e $variable'

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.


Add a comment

Show form to leave a comment

Comments (newest first)

No comments yet.

RSS feed

Blog Tags:

  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