BreadcrumbHomeResourcesBlog A Developer's Guide To Building PHP APIs October 30, 2024 A Developer's Guide to Building PHP APIsPHP DevelopmentProductivityBy Guido FaeckeCreating 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.Table of ContentsPHP API Setup: What to Know Before You BeginUsing Mezzio as Backend FrameworkInstalling Swagger UIUsing Doctrine as the ORM for Database TransactionsSetting Up Swagger Documentation for the API EndpointEnabling Isolated Local Development With DockerUsing PSR-12 for Coding Standard and Git for Version Control SystemUsing Static Analysis MethodsFinal ThoughtsTable of Contents1 - PHP API Setup: What to Know Before You Begin2 - Using Mezzio as Backend Framework3 - Installing Swagger UI4 - Using Doctrine as the ORM for Database Transactions5 - Setting Up Swagger Documentation for the API Endpoint6 - Enabling Isolated Local Development With Docker7 - Using PSR-12 for Coding Standard and Git for Version Control System8 - Using Static Analysis Methods9 - Final ThoughtsBack to topPHP API Setup: What to Know Before You BeginStarting 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 APIsVarious 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 RequirementsTo create a PHP API, ensure you have:PHP 8.0 or higher, with essential extensions like ext-json and ext-pdoComposer to manage dependenciesA web server (Apache or nginx) with proper routing configurationOptional but beneficial libraries like roave/psr-container-doctrine, swagger-php, and development tools like PHP_CodeSniffer, PHPStan, and PHPUnitHow to Create APIs in PHP: Required Technologies for This GuideThe 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 ServicesMeet 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 ServicesBack to topUsing Mezzio as Backend FrameworkMezzio 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 ExampleTo install Mezzio, use Composer:composer create-project mezzio/mezzio-skeleton my-apiDuring setup, select FastRoute as the router. You can configure the initial structure based on your API needs, with specific endpoints and middlewares.Back to topInstalling Swagger UIDocumenting 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-phpAdd 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 topUsing Doctrine as the ORM for Database TransactionsDoctrine 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 ExampleInstall roave/psr-container-doctrine with:composer require roave/psr-container-doctrineSet 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 AttributesIn 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 topSetting Up Swagger Documentation for the API EndpointWe’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.phpThis 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 SwaggerTo 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 DocumentationTo 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 ExampleUse the following command to generate the OpenAPI documentation file:./vendor/bin/openapi --output public/openapi.json srcNow, 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-uiThen 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 topEnabling Isolated Local Development With DockerDocker 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 SetupHere'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 topUsing PSR-12 for Coding Standard and Git for Version Control SystemFollowing 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 topUsing Static Analysis MethodsStatic 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 topFinal ThoughtsPHP 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 RuntimesZend 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 FreeAdditional ResourcesGuide - Developing Web Applications With PHPOn-Demand Webinar - Developing Robust 12-Factor Web ApplicationsOn-Demand Webinar - The Consequences of Building PHP In-HouseBlog - PHP Debugging: Go Beyond Step Debuggers With ZendHQBlog - How To Use PHP and ReactJS to Build Modern Web AppsBlog - How to Develop a WordPress PluginBlog - Using Mezzio on IBM iBlog - Asymmetric Visibility in PHP 8.4: What It Means for PHP TeamsBack to top
Guido Faecke Professional Services Engineer, Zend by Perforce Guido Faecke is a seasoned Professional Services Engineer with over 35 years of experience in application development and architecture, including 25 years specializing in PHP. Passionate about Mezzio, Laminas, and VueJS, he actively contributes to open-source projects and is dedicated to teaching best practices, clean coding, coding standards, and database optimization. With a solid foundation in IBM i and RPG, Guido brings a wealth of knowledge to the tech community and empowers others to achieve excellence in software development.