Serverless PHP Applications on Digital Ocean Functions

Published On18 Jul 2022

Serverless PHP Applications on Digital Ocean Functions Digital Ocean Functions is a serverless compute product Digital Ocean recently launched. It supports PHP as one of the runtimes, along with Node.js, Python, and Go.

PHP's Request-Response model has always been akin to modern serverless functions, in that PHP and serverless functions are often short-lived actions, and that they are invoked in command-line, and more commonly, with an HTTP request. Each of these short-lived requests do not share data in-between unless they explicitly store data in a persistent media (such as on files or a database), which make them ideal to scale up and down depending on the demand.

Traditionally, running a PHP web application required running a web server that accepts requests, passes them to PHP (php-fpm server API for example), and returns the response from PHP back to the client. Apache, Nginx, and Caddy are some of the web servers software that can act as the "server". With Serverless function platforms, the "server" is managed by the platform provider (Digital Ocean Functions in this case).

Digital Ocean Functions competes with existing Serverless platforms such as Google Cloud Functions, Amazon Lambda, and Oracle Cloud Functions.

Apache Open Whisk is a free and open-source serverless platform that also supports PHP as a language run-time. Digital Ocean Functions uses Open Whisk to power their underlying infrastructure.

Getting Started with Digital Ocean Functions

Once a Digital Ocean account is created, or logged into the dashboard, head to the Functions section to create the first Digital Ocean Function.

Functions created from the "Functions" section are meant for testing purposes. Once a function is fully tested out, they can be deployed with the App Platform.

Pricing for PHP Functions

The pricing page shows a summary of the billing rates for functions. Digital Ocean Functions are billed by the memory usage multiplied by the duration of each invocation of the function. In a PHP context, the billable GiB-seconds can be calculated by the memory each PHP request consumes (which can be obtained with memory_get_usage()), times the duration of the request.

For example, if the PHP function invocation consumes 128 MiB of memory, and takes 100 ms to process, that consumes roughly 0.0125 GiB-Seconds (30/1024 * 100/1000). Digital Ocean's free tier offers up to 90,000 GiB-Seconds a month at no charge. This is roughly equivalent to invoking said PHP function for over 7 million times a month.

Launch a test Serverless PHP Function

Head over to the Functions section in the dashboard, and click "Launch" to launch the first function.

The functions listed in the "Functions" section are meant to be testing, until they are deployed with the App Platform. In a production application, the application source code is taken from a GitHub repository, and built and deployed automatically when new code is pushed to that repository.

For the purpose of the first example, create a new function with the "Create Function" under "Prototype Serverless Functions" section:

Launch Serverless PHP Applications on Digital Ocean Functions
Click Create Function to create a new prototype serverless function
The next screen allows selecting the runtime (Php:8.0) and asks for a name for the function. This name will be part of the URL, but because they are prototype functions, they will be invoked at a namespace assigned to the Digital Ocean account.

Launch Serverless PHP Applications on Digital Ocean Functions
Select PHP runtime and give the function a name
Once the function is created, it is possible to modify the PHP source code of the serverless function. The "Settings" tab provides controls to edit the memory and duration limits, to edit function-specific environment variables, and to modify how the HTTP requests are handled.

Launch Serverless PHP Applications on Digital Ocean Functions
Edit the PHP source code of the prototype serverless function

Developing Serverless PHP Function

Digital Ocean Functions is based on Apache Open Whisk, and many concepts used in Open Whisk work on Digital Ocean Functions as well.

Each Function must have a function that serves as the entry-point, and that function must return an array containing the response body. The return value may additionally contain a statusCode for the HTTP response code, and a PHP stdClass object of HTTP response headers.

On PHP applications that are deployed on the App Platform, the code is fetched from a GitHub/GitLab repository, from Digital Ocean container registry, or from a Docker image. For Prototype functions, the source is directly entered at the "Source" tab of each function.


Here is a minimal yet functional "Hello World" example:

function main(): array {
    return [
        'body' => 'Hello World'
    ];
}

If statusCode is not provided, a standard "200 OK" response is sent to the client. Additional response headers can be added to from the code itself, or from CORS settings when the application is deployed with the App Platform.


Here is a minimal example that the function returns an error repose along with an HTTP status code and additional HTTP headers:

function main(): array {
    return [
        'body' => 'The requested resource is not found.',
        'statusCode' => 404,
        'headers' => (object)
            [
                'Cache-Control' => 'no-cache',
            ],
    ];
}

For prototype functions, the PHP source code can be edited and executed immediately.

Edit Serverless PHP Applications source code on Digital Ocean Functions
Edit and Run prototype function source code
Once executed, the output is shown directly below the source code:

See output of Serverless PHP Applications on Digital Ocean Functions
Output is shown below the source code editing area

PHP Runtime Environment

As of now, Digital Ocean's PHP runtime for Serverless Functions is configured as below:

PHP Version: 8.0 Available extensions: Libxml, OpenSSL, Sqlite3, Zlib, Curl, Dom, FTP, Iconv, Mbstring, PDO (Sqlite, PgSQL, MySQL), Session, Readline, SimpleXML, Phar, Tokenizer, XML, BCMath, GD, Intl, MongoDB, MySQLi, Soap, Sodium, Zip, Opcache

A few notable observations include:

  • max_execution_time and memory_limit PHP INI values being set to unlimited.
  • File uploads and allow_url_fopen enabled.
  • No functions disabled from INI settings.
  • error_reporting value set to E_ALL.

Digital Ocean may add new PHP versions as they see fit, and the list of extensions may change in the future as well.

HTTP Response Format

Each Digital Ocean Function should return a response value that may contain a body, an HTTP status code, and a list of HTTP responses.

For PHP functions, this means the invoked function should return a PHP associative array with body, statusCode, and headers.

function main(): array {
    return [
        'body' => 'Response body goes here',
        'statusCode' => 200,
        'headers' => (object)
            [
                'Cache-Control' => 'no-cache',
                'Foo' => 'Bar',
            ],
    ];
}
  • body: The response body as a string.
  • statusCode: The HTTP response code as an integer.
  • headers: A PHP stdClass object with the HTTP header names as object property names, and the header value as the property value.

Values emitted to the stdout (such as echo calls) will not be sent to the user.

HTTP Request Format

A serverless PHP can be invoked from various ways, such as calling it from CLI (using doctl), a REST API call, or via an HTTP request. HTTP request provide the ease of calling serverless functions from anywhere, and can be used with third party applications that only accept an HTTP endpoint.

The HTTP endpoint of the Serverless function is displayed above the function source code region. Any HTTP requests to this URL also invokes the function.

HTTP URL to invoke the serverless function
HTTP URL to invoke the serverless function
Digital Ocean Functions inherit Open Whisk's behavior that the invoked function receives the HTTP request information in the arguments passed to the function.

HTTP request information is only available if the function is invoked over HTTP/HTTPS. The following values will not be present if the function is invoked with the REST API or via doctl.

For each function invoked over HTTP, the following parameters are made available in addition to the application parameters passed in the URL or in the POST request body.

  • __ow_method: The HTTP method of the request, in lower case. e.g post, get.
  • __ow_headers: An associative array of HTTP headers from the HTTP request.
  • __ow_path: The path of the request. For requests made to the exact URL endpoint of the function, this value will be an empty string. If there is a URL path after the endpoint, that additional path will be stored here.

Digital Ocean Functions parse the HTTP request body (URL parameters or parameters of a POST request), but there is a setting under the "Settings" tab to opt-in to Raw HTTP, which leaves it up the function to parse the request body. With Raw HTTP enabled, the two following keys will be available in the parameters array in addition to the values listed above:

  • __ow_query: The HTTP query string of the request. e.g. name=test&username=test.
  • __ow_body: Contents of the raw HTTP request body, as a string.

Deploying PHP Serverless Functions

Prototype functions used in the section above are meant to be used as a testing ground until they are deployed on the App platform. Applications deployed on the App platform, support custom domain names, resource management, automatic deployment on Git commits, and more.

The rest of this article guides through an example application called "qr-show", a simple application that encodes a given string in a QR code.

project.yml File and the Git Repository

The app starts with a project.yml file, which is greatly explained in Digital Ocean docs. The following is the bare minimum to get started with PHP serverless functions:

In an empty or existing Git repository, place this project.yml file at the root, and have the changes pushed to a GitHub/GitLab repository.


project.yml

packages:
  - name: qr
    actions:
      - name: qrShow 
        runtime: php:default

In the example above, project.yml file describes a serverless function at qr/qrShow path, using php:default runtime. By default, this means the function will be invoked when requesting https://example.com/qr/qrShow URL, using the php:default runtime.

Digital Ocean docs explains the syntax and more parameters of the project.yml file in detail.

Creating Resources

To deploy a PHP serverless function on Digital Ocean App Platform, head over to the Apps page in the dashboard, and click "Create".

HTTP URL to invoke the serverless function
Starting a new app on App Platform with a GitHub repository
The next step offers an overview of the current resource allocations and an estimation of costs:

HTTP URL to invoke the serverless function
Resources allocated for the runtime
The next step offers controls to set environment variables for the app itself and for the serverless function, followed by a choice of data center location and the name of the application. Proceed with the form, and create the resources.

Application code

After creating the compute resources on Digital Ocean, it is now time to create/update application code to suit Digital Ocean Functions.

The following is an overview of the example QR code application that the article guides through:

  ├── packages
  │     └── qr
  │          └── qrShow
  │               ├── build.sh
  │               ├── composer.json
  │               ├── composer.lock
  │               └── index.php
  └── project.yml

PHP Source Files

PHP file(s) for the function shall be placed in packages/qr/qrShow directory. Digital Ocean Functions automatically assumes the main entry file to be packages/qr/qrShow/index.php, and calls the main() function defined in this file.

For the QR code example, here is a simple file that makes use of library to generate the QR code as an SVG string, and returned to the caller:


packages/qr/qrShow/index.php

<?php

use splitbrain\phpQRCode\QRCode;

function main(array $args): array {
    if (!isset($args['message']) || !is_string($args['message'])) {
        $response['statusCode'] = 400;
        $response['body'] = 'No "message" parameter provided.';
        return $response;
    }

    return ['body' => QRCode::svg('hello world')];
}

build.sh File

In the same directory as the index.php file, place a build.sh file that Digital Ocean Functions automatically execute when deploying. This function can be used to install Composer dependencies, run tests, etc.


packages/qr/qrShow/build.sh

#!/bin/bash

set -e

composer install --no-interaction --ansi
composer ci # Run a composer script named "ci", that runs tests, QA, etc.

composer.json and composer.lock

Digital Ocean Functions can be used to automatically install dependencies of the PHP serverless function. The application composer.json file can be placed in the same directory as index.php and build.sh files. The build.sh file in this example is set to run composer install to install the application dependencies at deploy time.

To ensure the deployed application uses the exact same set of dependencies everywhere, make sure to commit the composer.lock file to the repository as well.


packages/qr/qrShow/composer.json

{
    "require": {
        "php": "^8.0.0",
        "splitbrain/php-qrcode": "^0.1"
    }
}

Databases, Persistent File storage, Domain names

Digital Ocean offers several other features such as custom domain names, databases, static file storage, and other products that can be integrated with each other.

The Settings tab of each application provides fine control over the alert policies, custom domains, and environment variables.

Using the "Create" button, it is possible to create a new managed database instance that acts as a persistent database storage for serverless functions.

Logs and Insights

The Insights, Activity, and Runtime Logs sections provide detailed information on the performance, latency, and frequency information of the serverless functions, activity logs (when builds were triggered), and detailed logs to debug any failures or notices on builds.


Digital Ocean Functions is a serverless compute product from Digital Ocean that provides PHP as a run-time. It offers 90,000 GiB-seconds of compute resources for free every month, and PHP applications that are meant to be short-lived, with no or small requirement for persistent storage might be an ideal fit to deploy to Digital Ocean Functions as an inexpensive and scalable platform.

Recent Articles on PHP.Watch

All ArticlesFeed 
PHP 8.4 Installation and Upgrade guide for Ubuntu and Debian

PHP 8.4 Installation and Upgrade guide for Ubuntu and Debian

A guide for Debian and Ubuntu on how to install PHP 8.4 on a new server or how to upgrade an existing PHP setup to PHP 8.4.
How to fix `mysql_native_password` not loaded errors on MySQL 8.4

How to fix mysql_native_password not loaded errors on MySQL 8.4

How to fix the SQLSTATE[HY000] [1524] Plugin 'mysql_native_password' is not loaded errors caused in MySQL 8.4 no longer enabling the mysql_native_password plugin by default.
How to fix PHP Curl HTTPS Certificate Authority issues on Windows

How to fix PHP Curl HTTPS Certificate Authority issues on Windows

On Windows, HTTPS requests made with the Curl extension can fail because Curl has no root certificate list to validate the server certificates. This article discusses the secure and effective solutions, and highlights bad advice that can leave PHP applications insecure.
Subscribe to PHP.Watch newsletter for monthly updates

You will receive an email on last Wednesday of every month and on major PHP releases with new articles related to PHP, upcoming changes, new features and what's changing in the language. No marketing emails, no selling of your contacts, no click-tracking, and one-click instant unsubscribe from any email you receive.

Support PHP.Watch — If you find the articles, version information, Codex, and other PHP.Watch contributions useful, consider supporting through GitHub Sponsors. Your sponsorship helps dedicate more time to creating valuable content and improving the PHP community. Together, we can keep the momentum going — thank you for your support!

Thanks to the highest tier sponsor: @TomasVotruba for your generous support to keep PHP.Watch moving 💜