NewtFire logo: a mosaic rendering of a firebelly newt
newtFire {dh}
Creative Commons License Last modified: Friday, 29-Oct-2021 06:25:27 UTC. Maintained by: Elisa E. Beshero-Bondar (eeb4 at psu.edu). Powered by firebellies.

PHP, eXist, and your Website

PHP can help mediate between a database and your website, to allow for dynamic querying from a form on your website to trigger a script that returns data from your database. This means that your site can update its contents as you update the data in your database, so you do not have to post the results of a query by hand every time there is a change to the data. In the context of XML databases and eXist-dB, this means you and your team and be updating the XML under the hood of the database without needing to regenerate the HTML to deliver to your website.

Our tutorial introduces how to work with PHP in the context of eXist-dB and an Apache HTTP web server. The PHP is written to call out to the eXist-dB to run XQuery scripts and deliver content to a designated portion of an HTML page.

This Newtfire tutorial is based on one developed by David Birnbaum for Obdurodon while I was implementing PHP for the Digital Mitford project. It is designed for a simpler context of supporting student projects that are written in home-cooked XML not in a namespace. If your XML was developed in TEI, the Obdurodon tutorial linked here is better for you to follow.

PHP and XQuery code for both examples below are posted on GitHub in our digitProjectDesign-Hub, filed under Class Examples >> PHP-XQuery.

Overview

Our example is developed for the the Behrend Trees project, and the example is this PHP page containing a dynamically generated set of search options over a series of HTML tables.

The dynamic activities on this page are scripted out like this:

  1. On load of the page, PHP sends a request to the eXist-dB to populate the search menu options and the table contents.
  2. JavaScript then handles the interaction on the page based on the loaded data.

How PHP talks to eXist

PHP communicates with eXist using REST calls (short for Representational State Transfer) designed to help mediate data exchange over the web. To make this work, we first need to install PHP on the Newtfire server (which we did on building the server), and then we need to add a configuration file in the btrees project web space to communicate with eXist-dB at its RESTful address. For security reasons, we will not post the actual configuration file here, but it would look something like this, and it would be saved as config.php together with your project web files on the newtfire.org server.

            <?php
    define("REST_PATH", "http://fakeServerAddress.fake.org:8080/exist/rest");
?>

In the configuration file, we have defined a PHP variable to store our REST path to the eXist-dB. (If we ever need to move servers or change the database, we only have to update the config.php file to point to the new address, and the rest of the PHP, wherever we write it, will work.)

We will then call on this variable as REST_PATH in the PHP code we set up in the treesScrollToTable PHP page. The PHP file is basically written just like HTML, but contains an inset PHP script at the point in the page that you wish to populate with data from the database:

                  <?php
    require_once("config.php");
    $contents = REST_PATH . "/db/btrees-queries/btreesPHP-scrollJS.xql";
    $result = file_get_contents($contents);
    echo $result;
?>

This code then pulls on an script stored in the /db/btrees-queries/ directory in our eXist-dB, and it populates the menu selection options available as well as the tables of data. Unlike other XQuery scripts that output an entire HTML file, this one is set only to output the two portions of the document we are trying to populate. We wrapped the entire output in a single div element as a container for the XQuery to populate. Our XQuery code looks like this:

            xquery version "3.1";
declare variable $btrees := collection('/db/btrees/')/*;
declare variable $entries := $btrees//Q{}entry;
<div>
<section id="selection">
     <label for="commonChoice">Choose the common name of a tree: </label>
        <input id="commonChoice" list="common-names" />
        
        <datalist id="common-names">
        
        { 
            for $e in $entries
            let $cname := $e/Q{}cname ! data()
            return 
        
            <option id="c_{$e/@xml:id}" value="{$cname}"/>
        }
        </datalist>
  <label for="scientChoice">Choose the scientific name of a tree: </label>
        <input id="scientChoice" list="scientific-names" />
        
        <datalist id="scientific-names">
        
        { 
            for $e in $entries
            let $sname := $e/Q{}sname ! data()
            return 
        
            <option id="s_{$e/@xml:id}" value="{$sname}"/>
        }
        </datalist>
        </section>
{
for $e in $entries
let $cname := $e/Q{}cname ! data() 
let $sname := $e/Q{}sname ! normalize-space()
let $desc := $e/Q{}desc ! normalize-space()
let $type := $e/Q{}treeType ! normalize-space()
let $status := $e/Q{}status ! normalize-space()
let $origin := $e/Q{}origin ! normalize-space()
let $height := $e/Q{}height ! normalize-space()
let $seed := $e/Q{}seed ! normalize-space()
let $leaf := $e/Q{}leaf ! normalize-space()
return 
<table id="{$e/@xml:id}">
    <tr>
       <th>Common Name</th>
       <td>{$cname}</td>
    </tr>
    <tr>
        <th>Scientific Name</th>
        <td>{$sname}</td>
    </tr>
    <tr>
        <th>Tree Type</th>
        <td>{$type}</td>
    </tr>
    <tr>
        <th>Conservation Status</th>
        <td>{$status}</td>
    </tr>
    <tr>
        <th>Origin Location</th>
        <td>{$origin}</td>
    </tr>
    <tr>
        <th>Height</th>
        <td>{$height}</td>
    </tr>
    <tr>
        <th>Seed Type</th>
        <td>{$seed}</td>
    </tr>
    <tr>
        <th>Leaf Description</th>
        <td>{$leaf}</td>
    </tr>
    <tr>
        <th>Description</th>
        <td>{$desc}</td>
    </tr>
    <tr class="deco">
        <td></td>
        <td><img src="leaf_breaker.png" alt="decorative leaf"/></td>
    </tr>
</table>
}

</div>
         

In the XQuery code we plant the HTML attributes (the crucial @id on each table for each tree entry) for the JavaScript to function as we wish. Of course, to test the JavaScript as we are working on it, we will need to experiment on a full HTML file output that we save locally. (Otherwise, we could only see the JavaScript interaction on the published newtfire.org server.) Once we have the JavaScript and CSS working, we implement the PHP as shown here.

PHP that takes a variable as input and sends it to eXist-dB

Page 1: Preparing the clickable options to be displayed

In this section we will demonstrate how to take a variable input to a form, send it to eXist-dB to run an XQuery lookup on that input. To do this safely we will need to sanitize the input using a special function to alter inputs that could be damaging. For this section we turn to a student project investigating the history of manufacturing Nissan cars. We set in motion a chain of events using PHP beginning with a launch page. That page is populated with data using a PHP include method. The PHP of our launch page, index.php, looks like this:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Find Cars by Year</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="stylesheet" type="text/css" href="webstyle.css"/>
    </head>
    <body>
        <h1>Choose a year below, to find out the cars manufactured in that year.</h1>
        <p>The list reflects what years we have currently have available in our records.</p>
        <hr/>
        <!--ebb: START the process by including the autoYears.php with its call to eXist-dB to output a list of years. 
            We're saving this file as index.php so it's the root of our directory for our php-example, understood as a starting point. 
        On newtfire, find this file published at https://newtfire.org/courses/tutorials/php-examples/
        -->
    
        <?php include("autoGetYears.php")?>

    </body>
</html>

Here we have opted to call on a new PHP include file called autoGetYears.php that contains only the list structure and its call to the eXist-dB to populate the list within the framework of our index.php file. Our autoGetYears.php file very simply initiates the conversation with the eXist-dB to execute the XQuery script indicated.

              
<ul>
<?php
    require_once("config.php");
    $year = htmlspecialchars($_GET["year"]);
    $contents = REST_PATH . "/db/2021-Dig400-Examples/autoPHPGetYears.xql";
    $result = file_get_contents($contents);
    echo $result;
?>
</ul>

Preparing Page 2: Delivering an input parameter with XQuery

Now, in the eXist-dB, the autoPHPGetYears.xql file outputs the distinct-values of the years in our collection and sorts them. And we structure the output to contain links that involve selecting the year and passing it to a new PHP file, autoGetCars.php. When the visitor clicks to select a year, that year is delivered by the hyperlink to the next PHP file as an input parameter. Here is the XQuery script:

        xquery version "3.1";
let $autocoll := collection('/db/auto/')/*
let $built := $autocoll//built
let $years := $built/@when ! string() =>  distinct-values() => sort()
for $y in $years
return 
<li>
    <a href="autoGetCars.php?year={$y}">{$y}</a>
<!--ebb: Nothing else goes in the body content EXCEPT our PHP call to the XQuery that populates our list of cars. -->
</li>

Notice how we create an input parameter for the PHP with this code: <a href="autoGetCars.php?year={$y}">{$y}</a>. The PHP parameter is named ?year and set to pick up the same year value that is the content of the link. That year value will be delivered to the next PHP file, autoGetCars.php, which will hand it over to the last XQuery script in our sequence, the one that looks up the cars manufactured in a given year. Here is our code for autoGetCars.php, which is now picking up the input parameter in a special way to sanitize its contents:

        <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Find Cars by Year</title>
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" type="text/css" href="webstyle.css"/>
</head>
<body>
<h1>Your search returned these cars</h1>
<hr/>
<ul>

<!--ebb: Here we will simply pull in the year parameter from our PHP conversation -->
<h1>Made in <?php require_once("config.php"); echo htmlspecialchars($_GET["year"])?></h2>

<!--ebb: Now, we make a PHP call to the XQuery that populates our list of cars.  -->
<?php
    require_once("config.php");
    $year = htmlspecialchars($_GET["year"]);
    $contents = REST_PATH . "/db/2021-Dig400-Examples/autoPHPGetCars.xql?year=$year";
    $result = file_get_contents($contents);
    echo $result;
?>
</ul>
<!--ebb: Here we'll let visitors choose a new year if they wish. -->
 <h1>Choose a year below, to find out the cars manufactured in that year.</h1>
        <p>The list reflects what years we have currently have available in our records.</p>
<!--ebb: Now we just set a PHP include to pull in the original autoGetYears.php inside this file. -->
<?php include("autoGetYears.php");?> 

</body>
</html> 
    

Notice this code: $year = htmlspecialchars($_GET["year"]);. With the PHP htmlspecialchars() function we sanitize the input parameter, $year, removing from it any special characters, angle brackets that are incompatible with our documents and replacing them with their corresponding escape characters. In a higher-security screening process, we would set a more agressive filter on the user input tto trap any problematic entries. These filters can be set on the client-side with JavaScript and HTML form validation, or the server-side: read more about them.

In the PHP file above, we have set the stage for the output of the list of cars manufactured in a given year. We also used <?php include();?> on the same output page to output all the years again, making it possible vor the visitor to choose a new year to continue the lookup and return of data from our eXist-dB. In eXist-dB the XQuery script that looks up the car information has to be coded to accept our input parameter and use it to filter the results it provides. The XQuery code that accepts the input parameter looks like this:

xquery version "3.1";
declare variable $year := request:get-parameter('year', '1968');
let $autocoll := collection('/db/auto/')/*
let $built := $autocoll//built
let $names:= $autocoll[.//built/@when ! string() = $year]//name
for $n in $names 
      return
        <li>{$n}</li>

In our XQuery we defined a global variable, $year to be the input year parameter delivered by the PHP. We also set a dummy default value from our data, and that default value is what will be tested and returned when we eval or run our XQuery in eXide.

Conclusion

For the conversation to between our web server and eXist-dB to work, we require a proper config.php file and any file with PHP script in it must be named with a .php file extension and saved in the web server. In eXist-dB, we need to make sure that the code we are generating fits into the structure of the PHP file so that the HTML portion of it will be well-formed. We can style a PHP file with CSS and apply JavaScript to it just as we do an HTML file. Working PHP can simplify the XQuery scripts we need to write, since XQuery will not need to output an entire HTML document, but can rather be designed to fill out the inner contents of a site.

What next? We recommend learning more about HTML form input elements and explore how they can work with PHP, perhaps providing data in dropdown lists using PHP Select Option, or working PHP calls to eXist into clickable radio buttons on your site. Think about how you can prepare form options for your visitors to interact with, and consider how you may use PHP together with JavaScript (as we did on our first example on this tutorial) to facilitate searching your pages and project data.