containers

Develop an HTTP API with Bitnami's PHP-FPM and Apache Containers

Introduction

Setting up an Apache + PHP environment to develop a custom PHP application can be a time-consuming process, especially if you’re doing it for the first time. If you’re new to the language and just looking to experiment, spending time and effort on this initial setup might be a major obstacle for you. And even if you’re an experienced developer, it’s always nice to have a pre-configured environment that you can jump into and begin coding immediately.

That’s where Bitnami’s PHP-FPM container comes in. It provides a ready-to-use PHP FastCGI implementation with some additional features useful for sites of any size, especially busier sites. It also comes with the latest version of PHP. Coupled with Bitnami’s Apache container, it lets you kick-start your PHP application development and start coding quicker and with less effort.

This article shows you how to:

  • Combine Bitnami’s Apache and PHP-FPM containers to quickly develop a custom HTTP API using PHP.
  • Handle related PHP configuration tasks in a containerized environment, such as:
    • Using Composer packages;
    • Configuring Apache virtual hosts;
    • Enabling Apache URL rewriting.

Example application

The goal of this article is to develop a simple HTTP API that generates universally unique identifiers (UUIDs) on request using PHP. To achieve this, you will use two MIT-licensed components:

  • Slim Framework, a PHP micro-framework designed for rapid development of Web applications and APIs. Slim works by routing HTTP requests to specific callbacks depending on their signature (request method and endpoint). This makes it ideal for REST APIs that rely on the HTTP request method (GET, POST, PUT and DELETE) to identify the type of operation being requested.

  • Ben Ramsey’s UUID library, a popular tool to generate UUIDs in PHP. It supports UUID versions 1, 3, 4 and 5. Read more about UUIDs.

Assumptions and prerequisites

This guide makes the following assumptions:

Step 1: Create the project working directory

Begin by creating a working directory for your application’s code on your Docker host, as shown below:

$ cd ~
$ mkdir myapp

Step 2: Configure and test Apache and PHP

The next step is to combine two Bitnami container images to create a full-fledged Apache/PHP development environment:

Perform the following steps:

  • Create a folder named apache-vhost in the working directory on the Docker host:

    $ mkdir ~/myapp/apache-vhost
    
  • Create a file named myapp.conf in the apache-vhost/ directory with the following content:

    LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
    <VirtualHost *:8080>
      DocumentRoot "/app"
      ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/app/$1
      <Directory "/app">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
        DirectoryIndex index.php
      </Directory>
    </VirtualHost>
    

    This configuration file tells Apache to pass all PHP files to PHP-FPM for processing using the FastCGI interface, and also sets the Apache document root for the virtual host to the /app directory. Notice that this configuration file uses php as the PHP-FPM host name.

  • Create a Docker Compose file named docker-compose.yml in the working directory on the Docker host and fill it with the following code:

    version: '2'
    
    services:
      php:
        image: bitnami/php-fpm:latest
        ports:
          - 9000:9000
        volumes:
          - .:/app
      apache:
        image: bitnami/apache:latest
        ports:
          - 80:8080
        volumes:
          - ./apache-vhost/myapp.conf:/vhosts/myapp.conf:ro
          - .:/app
        depends_on:
          - php
    

    This Docker Compose definition covers two services. Let’s look at each in detail.

    • The Apache service uses Bitnami’s Apache container image. By default, the container is configured to use port 8080 for HTTP requests. It’s also configured to create virtual hosts using definitions mounted at the /vhosts directory. This Docker Compose definition mounts the custom virtual host file created previously at this location, and exposes the HTTP service on port 80 of the Docker host.

      NOTE: Ensure that port 80 on the Docker host is not already in use and that your host’s firewall allows inbound access on that port.

    • The PHP-FPM service uses Bitnami’s PHP-FPM container image. By default, this container image assumes that the PHP application is located in the /app directory. This Docker Compose definition therefore mounts the current directory (containing the application source code) at the /app path of the container.

  • Create a simple PHP script to test your environment in the working directory:

    $ echo "<?php echo 'Hello'; ?>" > ~/myapp/index.php
    
  • Start the containers using Docker Compose:

    $ docker-compose up -d
    
  • Check that the containers are running:

    $ docker ps
    
  • Test that it works by browsing to the URL http://IP-ADDRESS/index.php, where IP-ADDRESS is the IP address of the Docker host or localhost if you are using a local Docker environment, and confirm that you see the output (“Hello”) of the PHP script in your Web browser.

Step 3: Install dependencies with Composer

The typical next step is to define the application’s dependencies and install them with Composer. For this example, you only need two Composer packages: Slim Framework and Ben Ramsey’s UUID library.

  • Create a composer.json file in the working directory with the following content:

    {
        "require": {
           "php": ">=7.2.0",
           "slim/slim": "^3.11",
           "ramsey/uuid": "^3.8"
        }
    }
    
  • Install dependencies using the Bitnami PHP-FPM container image, which has Composer built into it (run this command from the same directory as your docker-compose.yml file):

    $ docker-compose exec php composer install
    

    TIP: You can re-run the above command every time you update the composer.json file to add or remove Composer packages.

Step 4: Enable URL rewriting

Many PHP frameworks use the front controller design pattern, and Slim Framework follows the same approach. This involves rewriting URLs so that all requests are handled by the application’s index.php file.

The Apache virtual host configuration defined in Step 2 already allows overriding configuration with per-directory .htaccess files, so enabling URL rewriting can be easily achieved.

Create a .htaccess file in the application working directory on the Docker host and fill it with this content:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^ index.php [QSA,L]
</IfModule>

Step 5: Start developing the API

To use Slim Framework, you must define a callback for each supported endpoint and request method and write handler code that responds to the request. For this illustrative example, you only need to support one endpoint (assume it is /api/uuid/generate) and one request method (GET).

In the working directory on the Docker host, modify the index.php file to reflect the following content:

<?php
// autoload files
require 'vendor/autoload.php';

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\Exception\UnsatisfiedDependencyException;

// initialize Slim application instance
$app = new \Slim\App();

// API endpoint to obtain new UUID
$app->get('/api/uuid/generate', function (Request $request, Response $response) {
  try {
    $uuid = Uuid::uuid1();
    $data = ['uuid' => $uuid->toString()];
    return $response->withJson($data, 200);
  } catch (Exception $e) {
    $data = ['error' => $e->getMessage()];
    return $response->withJson($data, 500);
  }
});

// run application
$app->run();

This script begins by instantiating the Composer auto-loader and loading required dependencies. It then initializes a new Slim App instance and defines a callback to handle the /api/uuid/generate method.

The code within the callback uses the Ramsey UUID library to generate a v1 UUID (timestamp-based) and returns it as a JSON-encoded response packet with a 200 (OK) HTTP code. Errors, if any, are caught by the exception handler and returned with a 500 (Server error) HTTP code and a JSON-encoded error message.

Now, test your API by browsing to http://IP-ADDRESS/api/uuid/generate. You should see something like this:

API output

You can now continue developing the API, by adding more endpoints and functionality. Remember that the application code is stored on the Docker host and the Bitnami Apache container uses this same location as its document root. Therefore, every change you make can be immediately tested without requiring you to stop and restart the containers each time.

To learn more about the topics discussed in this guide, use the links below:

Last modification July 5, 2019