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