Back to Articles
35 min read

PHP API & CLI Development: Building RESTful Services, GraphQL, and Robust Console Tools

Modern backend engineering requires headless architecture. This guide provides a dual focus: first, on designing production-ready APIs (handling versioning, authentication, and protocols like gRPC); and second, on mastering the Command Line Interface to build interactive tools, daemons, and cron jobs that automate your infrastructure.

API Development

RESTful API Design

Use HTTP methods semantically (GET=read, POST=create, PUT/PATCH=update, DELETE=remove), proper status codes, and resource-based URLs.

GET /api/users → List users (200)
GET /api/users/123 → Get user (200, 404)
POST /api/users → Create user (201)
PUT /api/users/123 → Replace user (200)
PATCH /api/users/123 → Update user (200)
DELETE /api/users/123 → Delete user (204)

JSON Handling

json_encode() for output, json_decode() for input. Always set proper headers and handle errors.

// Response header('Content-Type: application/json'); echo json_encode(['data' => $users], JSON_THROW_ON_ERROR); // Request parsing $input = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);

XML Handling

SimpleXML for reading, DOMDocument for complex manipulation. Less common now but still used in legacy APIs.

// Reading XML $xml = simplexml_load_string($xmlString); echo $xml->user->name; // Creating XML $doc = new DOMDocument('1.0', 'UTF-8'); $root = $doc->createElement('response'); $doc->appendChild($root); header('Content-Type: application/xml'); echo $doc->saveXML();

API Versioning

Version APIs via URL path (/v1/users), header (Accept: application/vnd.api.v1+json), or query param. URL versioning is most common and discoverable.

// URL versioning (recommended) Route::prefix('api/v1')->group(fn() => require 'routes/api_v1.php'); Route::prefix('api/v2')->group(fn() => require 'routes/api_v2.php'); // Header versioning $version = $_SERVER['HTTP_API_VERSION'] ?? 'v1';

API Authentication (API Keys, OAuth 2.0, JWT)

API keys for simple auth, OAuth 2.0 for delegated access, JWT for stateless token-based auth.

// JWT validation $jwt = str_replace('Bearer ', '', $_SERVER['HTTP_AUTHORIZATION']); $payload = JWT::decode($jwt, new Key($secretKey, 'HS256')); // Simple API key $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? ''; if (!$db->isValidApiKey($apiKey)) abort(401);

Rate Limiting

Return 429 Too Many Requests with Retry-After header. Track by API key, IP, or user.

header('X-RateLimit-Limit: 100'); header('X-RateLimit-Remaining: ' . $remaining); header('X-RateLimit-Reset: ' . $resetTimestamp); if ($remaining <= 0) { header('Retry-After: 60'); http_response_code(429); exit(json_encode(['error' => 'Rate limit exceeded'])); }

API Documentation (OpenAPI/Swagger)

OpenAPI specification describes your API in YAML/JSON; tools generate interactive docs (Swagger UI) and client SDKs.

# openapi.yaml openapi: 3.0.0 paths: /users: get: summary: List all users responses: '200': description: Success content: application/json: schema: type: array items: $ref: '#/components/schemas/User'

HATEOAS

Hypermedia As The Engine Of Application State—responses include links to related actions/resources, enabling API discoverability.

{ "id": 123, "name": "John", "_links": { "self": { "href": "/api/users/123" }, "orders": { "href": "/api/users/123/orders" }, "update": { "href": "/api/users/123", "method": "PATCH" }, "delete": { "href": "/api/users/123", "method": "DELETE" } } }

GraphQL with PHP

Query language allowing clients to request exactly the data they need. Use webonyx/graphql-php or Lighthouse for Laravel.

use GraphQL\GraphQL; use GraphQL\Type\Schema; $schema = new Schema(['query' => $queryType]); $result = GraphQL::executeQuery($schema, $query, null, null, $variables); echo json_encode($result->toArray()); // Client query // { user(id: 123) { name, email, orders { id } } }

gRPC with PHP

High-performance RPC framework using Protocol Buffers. Use grpc/grpc extension and grpc_php_plugin.

// user.proto service UserService { rpc GetUser (UserRequest) returns (User); } message User { int32 id = 1; string name = 2; }
$client = new UserServiceClient('localhost:50051', ['credentials' => ...]); $response = $client->GetUser(new UserRequest(['id' => 123]))->wait();

Webhook Implementation

HTTP callbacks that notify external systems of events. Include signatures for verification and implement retry logic.

// Sending webhook $payload = json_encode(['event' => 'order.created', 'data' => $order]); $signature = hash_hmac('sha256', $payload, $secretKey); $ch = curl_init($webhookUrl); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'X-Signature: ' . $signature ] ]);

CLI Development

PHP CLI Basics

Run PHP scripts from command line with php script.php. Access arguments via $argv/$argc, use STDIN/STDOUT/STDERR for I/O.

#!/usr/bin/env php <?php // cli.php echo "Script: " . $argv[0] . PHP_EOL; echo "Arguments: " . ($argc - 1) . PHP_EOL; fwrite(STDOUT, "Hello World\n"); fwrite(STDERR, "Error message\n"); exit(0); // 0 = success, non-zero = error

Argument Parsing

Use getopt() for simple parsing, or libraries for complex needs.

// php cli.php -v --name=John --output=/tmp $options = getopt('v', ['name:', 'output::', 'help']); // : = required, :: = optional $verbose = isset($options['v']); $name = $options['name'] ?? 'default'; // Short: -v, -n value // Long: --verbose, --name=value

Symfony Console Component

Industry-standard library for building CLI applications with commands, arguments, options, and rich output.

use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class GreetCommand extends Command { protected static $defaultName = 'app:greet'; protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln('<info>Hello World!</info>'); return Command::SUCCESS; } }

Interactive Commands

Prompt users for input using Symfony Console's QuestionHelper or built-in readline().

use Symfony\Component\Console\Question\{Question, ConfirmationQuestion, ChoiceQuestion}; $helper = $this->getHelper('question'); $name = $helper->ask($input, $output, new Question('Name: ')); $confirm = $helper->ask($input, $output, new ConfirmationQuestion('Continue? [y/N] ')); $color = $helper->ask($input, $output, new ChoiceQuestion('Color?', ['red', 'blue', 'green']));

Progress Bars and Output Formatting

Visual feedback for long-running tasks using Symfony Console's ProgressBar and styling.

use Symfony\Component\Console\Helper\ProgressBar; $progress = new ProgressBar($output, count($items)); $progress->start(); foreach ($items as $item) { process($item); $progress->advance(); } $progress->finish(); // Output styling $output->writeln('<info>Success</info>'); // Green $output->writeln('<error>Error</error>'); // Red bg $output->writeln('<comment>Note</comment>'); // Yellow

Background Processes

Run processes without blocking using &, pcntl_fork(), or process managers like Supervisor.

// Simple background execution exec('php long_task.php > /dev/null 2>&1 &'); // Using pcntl_fork $pid = pcntl_fork(); if ($pid === 0) { // Child process doHeavyWork(); exit(0); } // Parent continues immediately

Daemon Scripts

Long-running processes that run continuously in background, typically managed by Supervisor or systemd.

// daemon.php declare(ticks=1); pcntl_signal(SIGTERM, fn() => exit(0)); while (true) { $job = $queue->pop(); if ($job) { processJob($job); } else { sleep(1); } }
; /etc/supervisor/conf.d/worker.conf [program:worker] command=php /app/daemon.php autostart=true autorestart=true numprocs=4

Cron Jobs

Scheduled tasks using system cron. Store cron expressions in code or use scheduling libraries.

# crontab -e * * * * * /usr/bin/php /var/www/app/artisan schedule:run >> /dev/null 2>&1 0 0 * * * /usr/bin/php /var/www/app/scripts/daily-report.php
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6)
│ │ │ │ │
* * * * * command