FileMaker and PHP 7

At the Custom Web Publishing (CWP) user group at FileMaker DevCon 2017 in July, a number of speakers (and a big thank you to everyone for a great session!) discussed solutions they’ve taken or investigated for making CWP apps compatible with PHP 7.

At Beezwax, we have a client project with a requirement for PHP 7 compatibility, but many of the available approaches were closed to us due to various constraints, so we came up with this solution.


Last year, we encountered an issue with how FileMaker’s XML API and PHP SDK handle ampersands in valuelists, with the result that for this project we are already using a custom-modified FileMaker PHP SDK.

(A brief digression on nomenclature: The PHP interface provided by FileMaker is commonly referred to as the “PHP API”. The FileMaker CWP engine exposes an XML API. The PHP libraries from FileMaker provide a PHP FileMaker object that exposes an API to the PHP environment. For a general term that describes this PHP library that knows how to talk to the XML API, I believe “PHP SDK” is a more accurate and less ambiguous term than “PHP API”, and that’s the term I’ll be using here.)

We found that making our already-customized PHP SDK compatible with PHP 7 was as simple as fixing a single type of syntax error.

Here’s a proof of concept script to demonstrate the the various warnings and errors generated under different versions of PHP:

<?php
ini_set('error_reporting', E_ERROR);

require 'FileMaker.php';

$fm = new FileMaker('php7-test','localhost:9999','test','test');

$find = $fm->newFindCommand('test');
$find->addFindCriterion('id', '== 1');
$result = $find->execute();

if($fm->isError($result)) {
  echo $result->getCode() . ' ' . $result->getMessage();
  exit(1);
}

$record = $result->getFirstRecord();
echo $record->getField('text') . "\n";

 

When I run this with PHP 5.6, I have no issues:

$ php test.php
Hello World!

 

If, however, I turn up the error level, I’ll get lots of warnings about deprecated syntax that will turn into errors in PHP 7.

ini_set('error_reporting', E_ALL);
$ php test.php

PHP Deprecated: Assigning the return value of new by reference is deprecated in /Users/colin_l/dev/fmphp-blog-test/FileMaker/Implementation/Parser/FMResultSet.php on line 65

Deprecated: Assigning the return value of new by reference is deprecated in /Users/colin_l/dev/fmphp-blog-test/FileMaker/Implementation/Parser/FMResultSet.php on line 65
PHP Deprecated: Assigning the return value of new by reference is deprecated in /Users/colin_l/dev/fmphp-blog-test/FileMaker/Implementation/Parser/FMResultSet.php on line 72

Deprecated: Assigning the return value of new by reference is deprecated in /Users/colin_l/dev/fmphp-blog-test/FileMaker/Implementation/Parser/FMResultSet.php on line 72
PHP Deprecated: Assigning the return value of new by reference is deprecated in /Users/colin_l/dev/fmphp-blog-test/FileMaker/Implementation/Parser/FMResultSet.php on line 80

[... and dozens more lines of deprecation notices ...]

Hello World!

 

“Assigning the return value of new by reference is deprecated” — let’s have a look at that:

$ head -n65 FileMaker/Implementation/Parser/FMResultSet.php | tail -1
$Vb4a88417->_impl->_layout =& new FileMaker_Layout($this->_fm);

The problem is =& new  — instantiating a new object and assigning the result by reference. You can read more in the docs.

 

Now I’ll run the same script in my PHP 7 environment:

$ php test.php
PHP Parse error: syntax error, unexpected 'new' (T_NEW) in /home/clieberman/dev/phpfmtest/FileMaker/PEAR.php on line 557

 

That “unexpected new” is the result of the unfixed “Assigning the return value of new by reference” deprecations we saw in 5.6

$ head -n557 FileMaker/PEAR.php | tail -1
$a = &new $ec($code, $mode, $options, $userinfo);

 

(Aside: If you’re wondering why the files and lines in the 5.6 examples are different from the failure in the v7 example, I don’t know the answer to that. My 5.6 env is OSX, and my v7 env is RHEL, so that may have something to do with it.)

We can fix the syntax errors by using bash’s sed (stream editor) to do inline replacements:

$ while read file; do sed -i -E 's/= ?& ?new/= new/' "$file"; done < <(find . -name '*.php')

 

NB: you never want to run sed with -i and no argument: using regular expressions to write your code is a shooting offense in most jurisdictions. In this case we’re making an intentional choice to do what we normally wouldn’t.

The argument to -i is a file extension sed will use when saving modified versions of the files it’s processing (without -i, sed writes to STDOUT). Without an argument to -i, sed overwrites the original files. You can see why this is obviously a bad idea unless you really, really have a good reason.

If you’re unfamiliar with bash, what we’re doing is finding recursively from the current working directory all files whose names end in .php, then with each of those executing a regex substitution (-E tells sed to use extended, as opposed to basic, regular expression syntax).

 

And does the script run now?

$ php test.php
Hello World!

It does.

 

The sed command above is for Linux only. On macOS, a slightly different syntax is needed:

while read file; do LANG=c sed -i "" -E 's/=& ?new/= new/' "$file"; done < <(find . -name '*.php')

 

The two differences for macOS are the empty string argument to -i and the LANG environment variable.

macOS very reasonably doesn’t want to let you modify files inline accidentally, so -i requires the empty string argument.

The LANG environment variable is related to character encoding. You can read more about this on stackoverflow.

To be sure, solving your PHP 7 compatibility needs like this is a bad idea. Once you’ve started maintaining your own hacked version of the SDK, you’re locked into supporting it yourself. We went down this road because we’d already hacked the SDK to work around the ampersand valuelist bug mentioned earlier. An off-the-shelf replacement like Romain Dunand’s FileMaker PHP-API is likely a better choice.

Leave a Reply