A decorative image advertising the blog, "A Developer's Guide to Building PHP API"
October 10, 2024

A Developer's Guide to Building PHP APIs

PHP Development
Productivity

Creating Application Programming Interfaces (APIs) in PHP can be a powerful way to manage interactions between the backend of your application and various clients. Whether you’re building an API for a web app, mobile app, or third-party integration, certain best practices and tools can make the process smoother and the results more reliable. 

This blog provides a guide to walk you through setting up a robust REST PHP API. I begin with initial setup considerations and outline which technologies will be needed to follow along, then move through various fundamentals, tools, and frameworks for building PHP APIs.

Back to top

PHP API Setup: What to Know Before You Begin

Starting with API development in PHP requires a good understanding of both backend fundamentals and PHP-specific frameworks and tools. Key considerations include:

  • Purpose of the API Decide on the core functionality and goals of your PHP API. This will influence the structure, routes, and logic of the endpoints.
  • Security Requirements– consider implementing authentication (like OAuth or JWT) and limiting request rates to protect your resources.
  • Scalability – Choose tools and practices that allow the API to scale, especially if you expect high traffic.

Having a clear understanding of these factors before you begin can help you select the right tools and frameworks for your project.

Types of PHP APIs

Various types of PHP APIs are available for your project. Each has different strengths, so choose one that fits the use case of your application:

  • REST (Representational State Transfer) APIs are best when you need simplicity and compatibility across many platforms.
  • SOAP (Simple Object Access Protocol) APIs  are best for high-security, enterprise-grade applications.
  • GraphQL APIs are best when clients need control over data and complex querying.
  • JSON-RPC/XML-RPC are best for simple method-based calls and are typically used for internal applications.
  • gRPC APIs are best for high-performance needs in microservices or real-time applications.

For the purposes of this guide, we decided to use REST APIs.

PHP API Requirements

To create a PHP API, ensure you have:

  • PHP 8.0 or higher, with essential extensions like ext-json and ext-pdo
  • Composer to manage dependencies
  • A web server (Apache or nginx) with proper routing configuration
  • Optional but beneficial libraries like roave/psr-container-doctrine, swagger-php, and development tools like PHP_CodeSniffer, PHPStan, and PHPUnit

How to Create APIs in PHP: Required Technologies for This Guide

The goal is to create API endpoints with PHP 8.3, while using best practices in all aspects possible. That said, we will focus on technologies like Mezzio as the backend framework, Swagger to showcase the endpoints, Doctrine as the ORM for database transactions, Docker to enable isolated local development, PSR-12 for the coding standard and Git as our version control system. In addition, we also utilize Psalm, PHPStan and PHPCodesniffer for static analysis.

Innovate Faster and Cut Risk With Zend Professional Services

Meet your goals and save time with proven, comprehensive Zend Professional Services. From PHP LTS to migration support and beyond, our experts are in your corner.

Explore Professional Services

Back to top

Using Mezzio as Backend Framework

Mezzio is a micro-framework that’s ideal for building middleware applications like APIs. Its middleware approach allows you to create flexible routing and streamlined request/response handling.

CLI Setup Example

To install Mezzio, use Composer:

composer create-project mezzio/mezzio-skeleton my-api


During setup, select FastRoute as the router. You can configure the initial structure based on your API needs, with specific endpoints and middlewares.

Back to top

Installing Swagger UI

Documenting your API is critical, and Swagger makes it easy by providing a structured way to showcase each endpoint. Using Swagger helps developers understand the input and output requirements of your API. Install Swagger UI with the following:

composer require zircote/swagger-php


Add annotations to your Mezzio controller or handler methods to define each endpoint's parameters, responses, and request types. Run Swagger to generate a swagger.json file and load it in Swagger UI for a visual display.

Back to top

Using Doctrine as the ORM for Database Transactions

Doctrine is an Object Rational Mapping (ORM) software that simplifies interactions with databases by mapping PHP objects to database tables, which streamlines data management.

For database transactions in Mezzio, we’ll use roave/psr-container-doctrine, which integrates Doctrine ORM with PSR-11 containers.

CLI Setup Example

Install roave/psr-container-doctrine with:

composer require roave/psr-container-doctrine


Set up the configuration in Mezzio, and create entity classes for each database table. You can then use Doctrine’s repository pattern to handle database operations, making it easier to interact with data without writing raw SQL.

This package provides factories that allow you to configure and inject the Doctrine Entity Manager into your Mezzio application. After installing, add configuration settings for the database connection in a config/autoload/doctrine.global.php file.

Here is an example configuration:

<?php 
use Doctrine\DBAL\Driver\PDOMySql\Driver as PdoMySqlDriver;
 
return [ 
    'doctrine' => [ 
        'connection' => [ 
            'orm_default' => [ 
                'driverClass' => PdoMySqlDriver::class,
                'params' => [ 
                    'host'     => 'localhost', 
                    'port'     => '3306', 
                    'user'     => 'api_user', 
                    'password' => 'api_password', 
                    'dbname'   => 'api_db', 
                ], 
            ], 
        ], 
    ], 
];


Now, you can use the EntityManager provided by Doctrine in your services by retrieving it from the container. This setup is highly modular and aligns well with Mezzio's dependency injection approach.

Creating a Doctrine Entity With Attributes

In this example, we use PHP 8 attributes (e.g., #[ORM\Column(...)]) to define Doctrine metadata. Also as an important note, this post has four properties: id, title, content, and createdAt.

<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: "posts")]
class Post
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: "integer")]
    private int $id;
    #[ORM\Column(type: "string", length: 255)]
    private string $title;
    #[ORM\Column(type: "text")]
    private string $content;
    #[ORM\Column(type: "datetime")]
    private \DateTime $createdAt;
    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content = $content;
        $this->createdAt = new \DateTime();
    }
    public function getId(): int
    {
        return $this->id;
    }
    public function getTitle(): string
    {
        return $this->title;
    }
    public function setTitle(string $title): void
    {
        $this->title = $title;
    }
    public function getContent(): string
    {
        return $this->content;
    }
    public function setContent(string $content): void
    {
        $this->content = $content;
    }
    public function getCreatedAt(): \DateTime
    {
        return $this->createdAt;
    }
}
Back to top

Setting Up Swagger Documentation for the API Endpoint

We’ll create an endpoint in Mezzio that allows fetching a list of posts and document this endpoint with Swagger annotations.

Controller Example: src/Handler/PostHandler.php

This handler will serve as the controller for our GET /posts endpoint, where we retrieve all blog posts.

In this example, we will inject EntityManager into PostHandler to access the Post repository. The handle method fetches all posts and maps them to a structured array for the JSON response. We use the #[OA\Get(...)] attribute to document this endpoint in Swagger.

<?php
namespace App\Handler;
use App\Entity\Post;
use Doctrine\ORM\EntityManager;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Laminas\Diactoros\Response\JsonResponse;
use OpenApi\Attributes as OA;
class PostHandler
{
    private EntityManager $entityManager;
    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }
    #[OA\Get(
        path: "/posts",
        summary: "Retrieve all blog posts",
        tags: ["Posts"],
        responses: [
            new OA\Response(
                response: 200,
                description: "List of posts",
                content: new OA\JsonContent(
                    type: "array",
                    items: new OA\Items(ref: "#/components/schemas/Post")
                )
            )
        ]
    )]
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $postRepository = $this->entityManager->getRepository(Post::class);
        $posts = $postRepository->findAll();
        $postData = array_map(function (Post $post) {
            return [
                'id' => $post->getId(),
                'title' => $post->getTitle(),
                'content' => $post->getContent(),
                'createdAt' => $post->getCreatedAt()->format('Y-m-d H:i:s'),
            ];
        }, $posts);
        return new JsonResponse($postData);
    }
}


Defining the Post Schema for Swagger

To fully document the Post entity, we define it as a schema in Swagger so it can be referenced in multiple endpoints if needed. In this example, we'll be adding Swagger schema annotations to Post.php. Modify the Post entity with Swagger annotations as follows:

<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use OpenApi\Attributes as OA;
#[ORM\Entity]
#[ORM\Table(name: "posts")]
#[OA\Schema(
    schema: "Post",
    description: "Blog post entity",
    properties: [
        new OA\Property(property: "id", type: "integer", description: "ID of the post"),
        new OA\Property(property: "title", type: "string", description: "Title of the post"),
        new OA\Property(property: "content", type: "string", description: "Content of the post"),
        new OA\Property(property: "createdAt", type: "string", format: "date-time", description: "Creation timestamp of the post")
    ]
)]
class Post
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: "integer")]
    private int $id;
    #[ORM\Column(type: "string", length: 255)]
    private string $title;
    #[ORM\Column(type: "text")]
    private string $content;
    #[ORM\Column(type: "datetime")]
    private \DateTime $createdAt;
    // constructor and getter methods...
}


In this updated entity, we use the #[OA\Schema(...)] attribute to define the Post schema, giving Swagger a blueprint for the data structure. Each property is described with OA\Property attributes, specifying the property’s type and description.

Generating and Viewing Swagger Documentation

To view the Swagger UI, you’ll need to generate the OpenAPI documentation. This can be done using a Swagger PHP tool to parse your annotations and generate the openapi.json or swagger.json file.

CLI Example

Use the following command to generate the OpenAPI documentation file:

./vendor/bin/openapi --output public/openapi.json src


Now, you can serve this file using Swagger UI. If you have a Docker setup, you can run Swagger UI with:

docker run -p 8080:8080 -e SWAGGER_JSON=/usr/share/nginx/html/openapi.json -v $(pwd)/public:/usr/share/nginx/html swaggerapi/swagger-ui


Then visit http://localhost:8080 in your browser to see your API documentation.

This setup uses PHP 8 attributes for defining Doctrine ORM mappings. It also documents the API endpoints and entity structure with Swagger attributes. Finally, it enables a clear and interactive documentation using Swagger UI.

By combining Doctrine's attribute driver with Swagger documentation, you make your PHP API more understandable for other developers, allowing them to see what endpoints are available and what data they can expect.

Back to top

Enabling Isolated Local Development With Docker

Docker allows you to create isolated environments, reducing the chances of "it works on my machine" issues. You can define your application stack (PHP, database, etc.) in a docker-compose.yml file.

Example Docker Setup

Here's a basic docker-compose.yml setup for a PHP API:

version: '3.8' 
  services: php: 
    image: php:8.0-apache 
    container_name: php_api 
    volumes: 
      - .:/var/www/html 
    ports: 
      - "8080:80" 
  db: 
    image: mysql:8.0 
    container_name: mysql_db 
    environment: 
      MYSQL_ROOT_PASSWORD: secret 
      MYSQL_DATABASE: api_db 
      MYSQL_USER: api_user 
      MYSQL_PASSWORD: api_password 
    ports: - "3306:3306"

 

To start your containers, run:

docker-compose up -d 
Back to top

Using PSR-12 for Coding Standard and Git for Version Control System

Following a consistent coding standard, like PSR-12, improves code readability and maintainability. Use PHP_CodeSniffer to enforce PSR-12 in your project:

composer require "squizlabs/php_codesniffer=*" ./vendor/bin/phpcs --standard=PSR12 src/


For version control, Git is essential. Initialize a Git repository:

git init 
git add . 
git commit -m "Initial commit for API setup"


Push your code to a remote repository (e.g., GitHub, GitLab) for backup and collaboration.

Back to top

Using Static Analysis Methods

Static analysis tools can help identify issues early in development by examining code for potential bugs, code smells, and type errors. Some popular static analysis tools for PHP include PHPStan and Psalm.

Psalm is a static analysis tool for identifying issues, with a focus on improving type safety. PHPStan, meanwhile, helps find bugs in your codebase and can be installed with Composer:

composer require --dev phpstan/phpstan 
./vendor/bin/phpstan analyse src/

 

Using these tools during development ensures a high standard of code quality and reduces the risk of runtime errors.

Back to top

Final Thoughts

PHP APIs are a critical aspect of modern web development. This guide only scratches the surface of what's possible using the powerful tools and flexible approaches currently available for developers. 

However, there is no one-size-fits-all answer for PHP API development, and the best tools, practices, and methods will vary depending on your project goals, developer skill level, and more. It is my hope that this guide provides insight and inspiration as you set out to build PHP APIs that further your business.

Secured and Supported PHP Runtimes

Zend keeps your mission-critical PHP applications supported, secure, and compliant through ZendPHP runtimes. When paired with the ZendHQ extension, you access unparalleled observability and orchestration tooling. Ready to improve your PHP apps?

Explore ZendPHP Runtimes  Try ZendPHP + ZendHQ Free

Additional Resources

Back to top