PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
200: Building HATEOAS-Style APIs
Wait, why can't I just give the client a Swagger doc and let them build the URLs?
I get this one a lot. Why spend the extra effort embedding links in your JSON when you can just document your API endpoints and call it a day? The answer is decoupling. When you hardcode URLs into a frontend or a mobile app, you're creating a rigid contract. If you decide to change /api/orders/123/cancel to /api/orders/123/void, you've just broken every single client implementation.
With HATEOAS (Hypermedia as the Engine of Application State), the client doesn't need to "know" the URL. It only needs to know the relationship (the rel). The client looks for the link labeled "cancel" and follows whatever URL the server provided. If you change the URL on the backend, the client keeps working because it's just following the map you gave it in the response. It turns your API from a static set of endpoints into a navigable conversation.
How do I actually structure these links so they don't clutter my data?
You don't want your business data and your navigation links mashed together in one flat object. That's a nightmare to parse. The common practice is to wrap your links in a dedicated key, usually _links. This separates the "what this thing is" from "what I can do with this thing."
Let's look at an Order resource. If I'm requesting a specific order, I shouldn't just send the order details; I should send the actions available for that order's current state.
<?php
// A simplified Order Resource representation
$order = [
'id' => 5421,
'status' => 'pending',
'total' => 120.50,
'currency' => 'USD',
'_links' => [
'self' => [
'href' => '/api/orders/5421',
'method' => 'GET'
],
'cancel' => [
'href' => '/api/orders/5421/cancel',
'method' => 'POST'
],
'payment' => [
'href' => '/api/orders/5421/pay',
'method' => 'POST'
]
]
];
echo json_encode($order, JSON_PRETTY_PRINT);
?>
How do I handle the logic of which links to show and when?
This is where the "Application State" part of HATEOAS really kicks in. You shouldn't show a "cancel" link if the order has already been shipped. The server is the source of truth for business logic, so the server should decide which transitions are valid.
I usually recommend a small helper method or a dedicated Link Generator class. You don't want this logic leaking into your controllers. Here is how I'd handle the conditional logic for an order system:
<?php
class OrderLinkGenerator {
public function getLinks(Order $order): array {
$links = [
'self' => ['href' => "/api/orders/{$order->id}", 'method' => 'GET']
];
// Only allow cancellation if the order is still pending
if ($order->status === 'pending') {
$links['cancel'] = [
'href' => "/api/orders/{$order->id}/cancel",
'method' => 'POST'
];
}
// Only allow payment if it's pending and not already paid
if ($order->status === 'pending' && !$order->isPaid()) {
$links['pay'] = [
'href' => "/api/orders/{$order->id}/pay",
'method' => 'POST'
];
}
// Allow tracking only after it has been shipped
if ($order->status === 'shipped') {
$links['track'] = [
'href' => "/api/orders/{$order->id}/track",
'method' => 'GET'
];
}
return $links;
}
}
?>
Now, the frontend is incredibly simple: if (response._links.cancel) { showCancelButton(); }. The frontend no longer needs to know the business rules for when an order can be cancelled; it just reacts to the presence of the link.
📋 Practical Task
Exercise: Implementing State-Based Links for a Bank Account API
You are building a Banking API. You need to create a AccountLinkGenerator class that provides HATEOAS links based on the account's balance and status. Implement the following logic:
- All accounts: Should have a
selflink. - Active accounts: Should have a
depositlink. - Active accounts with a balance > 0: Should have a
withdrawlink. - Frozen accounts: Should NOT have deposit or withdraw links, but should have a
request_unfreezelink.
Create a PHP script that defines an Account class (with properties id, balance, and status) and the AccountLinkGenerator class. Test it by creating one "Active" account with a positive balance and one "Frozen" account, then print the resulting JSON for both.
There are no comments for now.