6.1. Interfacing PHP and Firebird

To communicate with a Firebird database, you will need a driver.

6.1.1. PHP Drivers for Firebird

Two free, downloadable drivers are available for interfacing with Firebird:

  • The Firebird/Interbase extension (ibase_ functions)

  • The PDO driver for Firebird

Firebird Client Library

Both drivers require that you have the fbclient.dll client library installed (fbclient.so for POSIX systems). Make sure it is for the correct CPU register width (32-bit or 64-bit) to match that of your web server/PHP installation. For example, if you have a 64-bit machine running 64-bit Firebird and 32-bit Apache/PHP then you need the 32-bit driver.

Note to Win32/Win64 users

For the drivers to work with the Windows PATH system variable, the fbclient.dll DLL file must be available. Copying the DLL file from the PHP directory or a Firebird installation to the Windows system folder would work, because the system directory is in the PATH variable by default. However, it is not recommended. The more robust way to do it is to prepend the file path to the PATH variable explicitly yourself, using the Windows advanced administration tool.

Make sure you have the matching release version of the Firebird client for your Firebird server.

The Firebird/InterBase Extension

The Firebird/Interbase (“Fb/IB”) extension predates the PDO driver and is regarded as the more proven solution of the two.

To install the extension, uncomment this line in the php.ini configuration file:

  1. extension=php_interbase.dll

or this line on Linux and other POSIX systems:

  1. extension=php_interbase.so
Installing the Fb/IB Extension on Linux

In Linux, one of the following commands should work. The one you use depends on the distribution package and the versions it supports:

  1. apt-get install php5-firebird
  2. rpm -ihv php5-firebird
  3. yum install php70w-interbase
  4. zypper install php5-firebird

You might need to enable third party repositories if you find you have unresolvable dependency problems.

Programming Style

The Firebird/InterBase extension uses a procedural approach to developing programs. Functions with the ibase_ prefix can return or accept the identifier (ID) of a connection, transaction, prepared query or cursor (the result of the SELECT query) as one of their parameters. This identifier is a server-allocated resource which, like all allocated resources, should be released immediately it is no longer needed.

The PHP functions will not be described in detail here. You can study their descriptions at https://php.net/ibase. Several small examples with comments will be provided instead.

  1. <?php
  2. $db = 'localhost:example';
  3. $username = 'SYSDBA';
  4. $password = 'masterkey';
  5. // Connect to database
  6. $dbh = ibase_connect($db, $username, $password);
  7. $sql = 'SELECT login, email FROM users';
  8. // Execute query
  9. $rc = ibase_query($dbh, $sql);
  10. // Get the result row by row as object
  11. while ($row = ibase_fetch_object($rc)) {
  12. echo $row->email, "\n";
  13. }
  14. // Release the handle associated with the result of the query
  15. ibase_free_result($rc);
  16. // Release the handle associated with the connection
  17. ibase_close($dbh);
The ibase_ Connect Functions

The ibase_pconnect function, that creates so-called “persistent connections”, could be used instead of ibase_connect. A call to ibase_close on this style of connection does not close it but all resources allocated to it will be released. The default transaction is committed, while any others are rolled back. This type of connection can be re-used in another session if the connection parameters match.

Persistent connections can increase the performance of a web application, sometimes considerably. It is especially noticeable if establishing a connection involves a lot of traffic. They allow a child process to use the same connection throughout its entire lifetime instead of creating a connection every time a page interacts with the Firebird server. Persistent connections are not unlike working with a connection pool.

You can find more details about persistent connections at https://php.net/persistent-connections.

Need to know

Many ibase_ functions cannot accommodate the identifier of a connection, transaction or prepared query. Those functions use the identifier of the last established connection or last started transaction instead of the relevant identifier. It is not a recommended practice, especially if your web application can use more than one connection.

ibase_query

The ibase_query function executes an SQL query and returns the identifier of the result or True if the query returns no data set. Along with the connection or transaction ID and the text of the SQL query, this function can accept a variable number of parameters to populate the SQL query parameters. For example,

  1. // …
  2. $sql = 'SELECT login, email FROM users WHERE id=?';
  3. $id = 1;
  4. // Execute query
  5. $rc = ibase_query($dbh, $sql, $id);
  6. // Get the result row by row as object
  7. if ($row = ibase_fetch_object($rc)) {
  8. echo $row->email, "\n";
  9. }
  10. // Release the handle associated with the result of the query
  11. ibase_free_result($rc);
  12. // …

Parameterized queries are typically used multiple times with fresh sets of parameter values each time. Prepared queries are recommended for this style of usage. The identifier of a query is returned by the function ibase_prepare and then the prepared query is executed using the function ibase_execute.

  1. // …
  2. $sql = 'SELECT login, email FROM users WHERE id=?';
  3. // Prepare statement
  4. $sth = ibase_prepare($dbh, $sql);
  5. $id = 1;
  6. // Execute statement
  7. $rc = ibase_execute($sth, $id);
  8. // Get the result row by row as object
  9. if ($row = ibase_fetch_object($rc)) {
  10. echo $row->email, "\n";
  11. }
  12. // Release the handle associated with the result of the query
  13. ibase_free_result($rc);
  14. // Release the prepared statement
  15. ibase_free_query($sth);

Prepared queries are very often used when a large amount of data input is anticipated.

  1. // …
  2. $sql = 'INSERT INTO users(login, email) VALUES(?, ?)';
  3. // Prepare statement
  4. $sth = ibase_prepare($dbh, $sql);
  5. $users = [["user1", "user1@gmail.com"], ["user2", "user2@gmail.com"]];
  6. // Execute statement
  7. foreach ($users as $user)) {
  8. ibase_execute($sth, $user[0], $user[1]);
  9. }
  10. // Release the prepared statement
  11. ibase_free_query($sth);
  12. // …

It is actually a disadvantage of this extension that functions can take a variable number of parameters. It less than ideal for parameterized queries, as the last example demonstrates. It is especially noticeable if you try to write a universal class for executing any query. It would be much more useful to be able to send parameters in one array.

This would be one way to get around it:

  1. function fb_execute ($stmt, $data)
  2. {
  3. if (!is_array($data))
  4. return ibase_execute($stmt, $data);
  5. array_unshift($data, $stmt);
  6. $rc = call_user_func_array('ibase_execute', $data);
  7. return $rc;
  8. }

The Fb/IB extension does not support named parameters in queries.

ibase_trans

By default, the Fb/IB extension commits the transaction automatically after executing each SQL query, making it necessary to start a transaction with the function ibase_trans if you need to control transactions explicitly. An explicit transaction is started with the following parameters if none are provided: IBASE_WRITE | IBASE_CONCURRENCY | IBASE_WAIT. You can find the description of predefined constants for specifying the parameters of a transaction here. A transaction must be completed by either ibase_commit or ibase_rollback.

This extension supports the COMMIT RETAIN and ROLLBACK RETAIN parameters directly if you use the functions ibase_commit_ret or ibase_rollback_ret, respectively, instead.

The default transaction parameters are good for most cases and it is really rarely that you need to change them. A connection to the database, along with all resources allocated to it, exists for no longer than it takes for the PHP script to complete. Even if you use persistent connections, all allocated resources will be released after the ibaseclose function is called. Even so, I strongly recommend releasing all allocated resources explicitly by calling the corresponding ibase functions.

I advise strongly against using the ibase_commit_ret and ibase_rollback_ret functions because they have no place in a web application. The purpose of COMMIT RETAIN and ROLLBACK RETAIN is to keep cursors open in desktop applications when a transaction ends.

  1. $sql = 'INSERT INTO users(login, email) VALUES(?, ?)';
  2. // Prepare statement
  3. $sth = ibase_prepare($dbh, $sql);
  4. $users = [["user1", "user1@gmail.com"], ["user2", "user2@gmail.com"]];
  5. $trh = ibase_trans($dbh, IBASE_WRITE | IBASE_CONCURRENCY | IBASE_WAIT);
  6. try {
  7. // Execute statement
  8. foreach ($users as $user)) {
  9. $r = ibase_execute($sth, $user[0], $user[1]);
  10. // If an error occurs, throw an exception
  11. if ($r === false)
  12. throw new \Exception(ibase_errmsg());
  13. }
  14. ibase_commit($trh);
  15. }
  16. catch(\Exception $e) {
  17. ibase_rollback($trh);
  18. echo $e->getMessage();
  19. }
  20. // Release the prepared statement
  21. ibase_free_query($sth);

ibase_ functions raise no exception if an error occurs, although an error will cause some to return False. Note that it is essential to use the === strict relational operator to compare the result to False. Calling any ibase function could result in an error.

The function ibase_errmsg is available to discover an error message and the function ibase_errcode can provide the error code.

Services API Calls

The Fb/IB extension can interact with the Firebird server by way of functions that wrap calls to the Services API: ibase_service_attach, ibase_service_detach, ibase_server_info, ibase_maintain_db, ibase_db_info, ibase_backup, ibase_restore. They can return information about the Firebird server, initiate a backup or restore or get statistics. We are not examining them in detail, since they are required mainly to administer a database, a topic that is outside the scope of this project.

Firebird Events

The Firebird/Interbase extension also supports working with Firebird events by means of a set of functions: ibase_set_event_handler, ibase_free_event_handler, ibase_wait_event.

The PDO (Firebird Driver)

The PDO extension is a common interface for accessing various types of databases. Each database driver that implements this interface can provide database-specific features in the form of standard extension functions.

PDO and all basic drivers are built into PHP as extensions. To use them, just enable them by editing the php.ini file as follows:

  1. extension=php_pdo.dll

This step is optional for PHP versions 5.3 and higher because DLLs are no longer needed for PDO to work.

Firebird-specific Library

The other requirement is for database-specific DLLs to be configured; or else loaded during execution by means of the dl() function; or else included in php.ini following php_pdo.dll. For example:

  1. extension=php_pdo.dll
  2. extension=php_pdo_firebird.dll

These DLLs must be in the directory extension_dir.

In Linux, one of the following commands should work. The one you use depends on the distribution package and the versions it supports:

  1. apt-get install php5-firebird
  2. rpm -ihv php5-firebird
  3. yum install php70w-firebird
  4. zypper install php5-firebird
Programming Style

PDO uses an object-oriented approach to developing programs. The DSN (Data Source Name), a.k.a. connection string, determines which specific driver will be used in PDO. The DSN consists of a prefix that determines the database type and a set of parameters in the form of <key>=<value> separated by semicolons. The valid set of parameters depends on the database type.

To be able to work with Firebird, the connection string must start with the firebird: prefix and conform to the format described in the PDO_FIREBIRD DSN section of the documentation.

Making Connections

Connections are established automatically during creation of the PDO from its abstract class. The class constructor accepts parameters to specify the data source (DSN) and also the optional username and password, if any. A fourth parameter can be used to pass an array of driver-specific connection settings in the key=value format.

  1. $dsn = 'firebird:dbname=localhost:example;charset=utf8;';
  2. $username = 'SYSDBA';
  3. $password = 'masterkey';
  4. try {
  5. // Connect to database
  6. $dbh = new \PDO($dsn, $username, $password,
  7. [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
  8. $sql = 'SELECT login, email FROM users';
  9. // Execute query
  10. $query = $dbh->query($sql);
  11. // Get the result row by row as object
  12. while ($row = $query->fetch(\PDO::FETCH_OBJ)) {
  13. echo $row->email, "\n";
  14. }
  15. $query->closeCursor();
  16. } catch (\PDOException $e) {
  17. echo $e->getMessage();
  18. }
Persistent connections

For PDO to use persistent connections, the array of attributes must be passed to the PDO constructor with PDO::ATTR_PERSISTENT ⇒ true.

Exception Handling

The PDO driver is much more friendly than the Firebird/InterBase extension with respect to exception handling. Setting the \PDO::ATTR_ERRMODE attribute to the value \PDO::ERRMODE_EXCEPTION specifies a mode in which any error, including a database connection error, will raise the exception \PDOException.

This is superior to the laborious procedure of checking whether an error has occurred each time an ibase_ function is called.

Querying

The query method executes an SQL query and returns the result set in the form of a \PDOStatement object. A fetch to this method can return the result in more than one form: it could be a column, an instance of the specified class, an object.

The various ways of calling query can be found in the documentation.

Queries with No Data Set

For executing an SQL query that returns no data set, you can use the exec method that returns the number of affected rows.

Executing prepared queries is not supported by exec.

Parameterized Queries

If there are parameters in the query, prepared queries must be used. For this, the prepare method is called instead of the query method. The prepare method returns an object of the \PDOStatement class that encapsulates methods for working with prepared queries and their results. Executing the query requires calling the execute method that can accept as its parameter an array of named or unnamed parameters.

The result of executing a SELECT query can be obtained with one the following methods: fetch, fetchAll, fetchColumn, fetchObject. The fetch and fetchAll methods can return results in various forms: an associative array, an object or an instance of a particular class. The class instance option is quite often used in the MVC pattern during work with models.

  1. $dsn = 'firebird:dbname=localhost:example;charset=utf8;';
  2. $username = 'SYSDBA';
  3. $password = 'masterkey';
  4. try {
  5. // Connect to database
  6. $dbh = new \PDO($dsn, $username, $password,
  7. [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
  8. $sql = 'INSERT INTO users(login, email) VALUES(?, ?)';
  9. $users = [
  10. ["user1", "user1@gmail.com"],
  11. ["user2", "user2@gmail.com"]
  12. ];
  13. // Prepare statement
  14. $query = $dbh->prepare($sql);
  15. // Execute statement
  16. foreach ($users as $user)) {
  17. $query->execute($user);
  18. }
  19. } catch (\PDOException $e) {
  20. echo $e->getMessage();
  21. }

An example using named parameters:

  1. $dsn = 'firebird:dbname=localhost:example;charset=utf8;';
  2. $username = 'SYSDBA';
  3. $password = 'masterkey';
  4. try {
  5. // Connect to database
  6. $dbh = new \PDO($dsn, $username, $password,
  7. [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
  8. $sql = 'INSERT INTO users(login, email) VALUES(:login, :email)';
  9. $users = [
  10. [":login" => "user1", ":email" => "user1@gmail.com"],
  11. [":login" => "user2", ":email" => "user2@gmail.com"]
  12. ];
  13. // Prepare statement
  14. $query = $dbh->prepare($sql);
  15. // Execute statement
  16. foreach ($users as $user)) {
  17. $query->execute($user);
  18. }
  19. } catch (\PDOException $e) {
  20. echo $e->getMessage();
  21. }

In order to support named parameters, PDO preprocesses the query and replaces parameters of the :paramname type with ‘?’, retaining the array of correspondence between the parameter names and their left-to-right positions in the query. For that reason, the EXECUTE BLOCK statement will not work if there are colon-prefixed variables. Currently, PDO offers no workaround to support a parameterized EXECUTE BLOCK statement, such as by specifying an alternative prefix for parameters as has been implemented in some access components.

Another Way to Do It

An alternative way to pass parameters to a query is by using “binding”. The bindValue method binds a value to a named or unnamed parameter. The bindParam method binds a variable to a named or unnamed parameter. The bindParam method is especially useful for stored procedures that return a value via the OUT or IN OUT parameter, which is different to the mechanism for returning values from stored procedures in Firebird.

  1. $dsn = 'firebird:dbname=localhost:example;charset=utf8;';
  2. $username = 'SYSDBA';
  3. $password = 'masterkey';
  4. try {
  5. // Connect to database
  6. $dbh = new \PDO($dsn, $username, $password,
  7. [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
  8. $sql = 'INSERT INTO users(login, email) VALUES(:login, :email)';
  9. $users = [
  10. ["user1", "user1@gmail.com"],
  11. ["user2", "user2@gmail.com"]
  12. ];
  13. // Prepare statement
  14. $query = $dbh->prepare($sql);
  15. // Execute statement
  16. foreach ($users as $user)) {
  17. $query->bindValue(":login", $user[0]);
  18. $query->bindValue(":email", $user[1]);
  19. $query->execute();
  20. }
  21. } catch (\PDOException $e) {
  22. echo $e->getMessage();
  23. }
Caution

The numbers associated with unnamed parameters for the bindParam and bindValue methods start from 1.

  1. $dsn = 'firebird:dbname=localhost:example;charset=utf8;';
  2. $username = 'SYSDBA';
  3. $password = 'masterkey';
  4. try {
  5. // Connect to database
  6. $dbh = new \PDO($dsn, $username, $password,
  7. [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
  8. $sql = 'INSERT INTO users(login, email) VALUES(?, ?)';
  9. $users = [
  10. ["user1", "user1@gmail.com"],
  11. ["user2", "user2@gmail.com"]
  12. ];
  13. // Prepare statement
  14. $query = $dbh->prepare($sql);
  15. // Execute statement
  16. foreach ($users as $user)) {
  17. $query->bindValue(1, $user[0]);
  18. $query->bindValue(2, $user[1]);
  19. $query->execute();
  20. }
  21. } catch (\PDOException $e) {
  22. echo $e->getMessage();
  23. }
Transactions

By default, PDO commits the transaction automatically after executing each SQL query. If you want to control transactions explicitly, you need to start a transaction with the method \PDO::beginTransaction. By default, a transaction is started with the following parameters: CONCURRENCY | WAIT | READ_WRITE. A transaction can be ended with the \PDO::commit or \PDO::rollback method.

  1. $username = 'SYSDBA';
  2. $password = 'masterkey';
  3. try {
  4. // Connect to database
  5. $dbh = new \PDO($dsn, $username, $password,
  6. [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
  7. // Start the transaction to ensure consistency between statements
  8. $dbh->beginTransaction();
  9. // Get users from one table
  10. $users_stmt = $dbh->prepare('SELECT login, email FROM old_users');
  11. $users_stmt->execute();
  12. $users = $users_stmt->fetchAll(\PDO::FETCH_OBJECT);
  13. $users_stmt->closeCursor();
  14. // And insert into another table
  15. $sql = 'INSERT INTO users(login, email) VALUES(?, ?)';
  16. // Prepapre statemenet
  17. $query = $dbh->prepare($sql);
  18. // Execute statememt
  19. foreach ($users as $user)) {
  20. $query->bindValue(1, $user->LOGIN);
  21. $query->bindValue(2, $user->EMAIL]);
  22. $query->execute();
  23. }
  24. // Commit transaction
  25. $dbh->commit();
  26. } catch (\PDOException $e) {
  27. // Rollback transaction
  28. if ($dbh && $dbh->inTransaction())
  29. $dbh->rollback();
  30. echo $e->getMessage();
  31. }

Changing Transaction Parameters

Unfortunately, the beginTransaction method does not permit transaction parameters to be changed, but you can do the trick by specifying transaction parameters in the SQL statement SET TRANSACTION.

  1. $dbh = new \PDO($dsn, $username, $password);
  2. $dbh->setAttribute(\PDO::ATTR_AUTOCOMMIT, false);
  3. $dbh->exec("SET TRANSACTION READ ONLY ISOLATION LEVEL READ COMMITTED NO WAIT");
  4. // Perform actions in the transaction
  5. // …
  6. $dbh->exec("COMMIT");
  7. $dbh->setAttribute(\PDO::ATTR_AUTOCOMMIT, true);

6.1.2. Comparing the Drivers

The following table summarises the capabilities offered by the two drivers for working with Firebird.

Table 7. Comparing the Firebird/InterBase and PDO Drivers
CapabilityFb/IB ExtensionPDO Extension

Programming paradigm

Procedural

Object-oriented

Supported database engines

Firebird and InterBase; or clones of either

Any database engine for which there is a PDO driver, including Firebird

Handling query parameters

Only unnamed parameters, not very convenient because the functions used allow the number of parameters to be variable

Can work with both named and unnamed parameters. Very convenient although some Firebird features (the EXECUTE BLOCK statement) do not work.

Error handling

Requires checking the results of the ibaseerrmsg, ibase_errcode functions. An error may occur after any ibase function call without raising any exception.

An optional mode is provided to raise exceptions on any error

Transaction management

Allows transaction parameters to be specified

Does not allow transaction parameters to be specified. Workaround: execute the SQL statement SET TRANSACTION.

Firebird-specific features

Supports work with the Services API (backup, restore, statistics, etc.) and with database events

Does not support any database-specific feature that cannot be implemented directly using an SQL statement

From these comparisons we can conclude that PDO is better equipped than the FB/IB extension for most frameworks.