Skip to content

Commit

Permalink
rewrite logic for the API to make errors clearer and to reduce code d…
Browse files Browse the repository at this point in the history
…uplication (e.g. `getHandler` was being duplicated in all api controllers)
  • Loading branch information
creme332 committed May 21, 2024
1 parent fcefbfe commit 9fb7fae
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 217 deletions.
77 changes: 66 additions & 11 deletions src/controllers/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ class API

public function __construct()
{
// Set the Content-Type header to application/json
header("Content-Type:application/json");

// Allow access from any origin (CORS)
header('Access-Control-Allow-Origin: *');

$this->resource = Utility::splitURL()[2] ?? "";
}

Expand All @@ -32,32 +36,83 @@ public function __construct()
*/
private function validateURLFormat(): bool
{
return preg_match("/^api\/v1/", $_GET["url"]) > 0;
return preg_match("/^api\/v1/", Utility::getURL()) > 0;
}


/**
* Returns the name of function responsible for handling the current request, as defined by the $routes variable.
* @param string $controllerName class name of controller
* @return string|null
*/
private function getHandler(string $controllerName): ?string
{
$all_routes = $controllerName::$routes;

// check if there are handlers defined for current request method
$my_routes = $all_routes[$_SERVER['REQUEST_METHOD']] ?? "";
if (empty($my_routes)) {
return null;
}

foreach ($my_routes as $route => $handler) {
$pattern = str_replace('/', '\/', $route); // Convert to regex pattern
$pattern = preg_replace(
'/\{([a-zA-Z0-9_]+)\}/',
'(?P<$1>[^\/]+)',
$pattern
); // Replace placeholders with regex capture groups
$pattern = '/^' . $pattern . '$/';

if (preg_match($pattern, '/' . Utility::getURL(), $matches)) {
return $handler;
}
}
return null;
}

public function index(): void
{
if (!$this->validateURLFormat()) {
http_response_code(400);
die();
return;
}

// call appropriate controller to handle resource
// check if there is a controller to handle resource
$controllerClassName = 'Steamy\\Controller\\API\\' . ucfirst($this->resource);
if (!class_exists($controllerClassName)) {
// no controller available
http_response_code(404);
echo 'Invalid resource: ' . $this->resource; // comment this line for production
return;
}

// determine which function to call in the controller to handle route
$functionName = $this->getHandler($controllerClassName);
if ($functionName === null) {
// Controller does not have any method defined for route
http_response_code(404);
echo "Request has not been defined in \$routes for " . $controllerClassName;
return;
}

$controller = new $controllerClassName();

if (!method_exists($controller, $functionName)) {
// handle function not found in controller
http_response_code(500);
echo $controllerClassName . ' does not have a public method ' . $functionName;
return;
}

// call function in controller for handling request
try {
if (class_exists($controllerClassName)) {
(new $controllerClassName())->index();
} else {
http_response_code(404);
die();
}
call_user_func(array($controller, $functionName));
} catch (Exception $e) {
http_response_code(500);

// Uncomment line below only when testing API
echo $e->getMessage();

die();
}
}
}
62 changes: 9 additions & 53 deletions src/controllers/api/Districts.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@ class Districts
{
use Model;

public static array $routes = [
'GET' => [
'/api/v1/districts' => 'getAllDistricts',
'/api/v1/districts/{id}' => 'getDistrictById',
]
];

/**
* Get the list of all districts available.
*/
private function getAllDistricts(): void
public function getAllDistricts(): void
{
// Retrieve all districts from the database
$allDistricts = District::getAll();
Expand All @@ -36,7 +43,7 @@ private function getAllDistricts(): void
/**
* Get the details of a specific district by its ID.
*/
private function getDistrictById(): void
public function getDistrictById(): void
{
$districtId = (int)Utility::splitURL()[3];

Expand All @@ -57,55 +64,4 @@ private function getDistrictById(): void
'name' => $district->getName()
]);
}

private function getHandler($routes): ?string
{
foreach ($routes[$_SERVER['REQUEST_METHOD']] as $route => $handler) {
$pattern = str_replace('/', '\/', $route); // Convert to regex pattern
$pattern = preg_replace(
'/\{([a-zA-Z0-9_]+)\}/',
'(?P<$1>[^\/]+)',
$pattern
); // Replace placeholders with regex capture groups
$pattern = '/^' . $pattern . '$/';

if (preg_match($pattern, '/' . Utility::getURL(), $matches)) {
return $handler;
}
}
return null;
}

/**
* Main entry point for the Districts API.
*/
public function index(): void
{
$routes = [
'GET' => [
'/api/v1/districts' => 'getAllDistricts',
'/api/v1/districts/{id}' => 'getDistrictById',
]
];

// Handle the request
$handler = $this->getHandler($routes);

if ($handler !== null) {
$functionName = $handler;
if (method_exists($this, $functionName)) {
call_user_func(array($this, $functionName));
} else {
// Handle function not found
http_response_code(404);
echo "Function Not Found";
die();
}
} else {
// Handle route not found
http_response_code(404);
echo "Route Not Found";
die();
}
}
}
90 changes: 23 additions & 67 deletions src/controllers/api/Products.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,27 @@ class Products
{
use Model;

public static array $routes = [
'GET' => [
'/api/v1/products' => 'getAllProducts',
'/api/v1/products/categories' => 'getProductCategories',
'/api/v1/products/{id}' => 'getProductById',
],
'POST' => [
'/api/v1/products' => 'createProduct',
],
'PUT' => [
'/api/v1/products/{id}' => 'updateProduct',
],
'DELETE' => [
'/api/v1/products/{id}' => 'deleteProduct',
]
];

/**
* Get the list of all products available in the store.
*/
private function getAllProducts(): void
public function getAllProducts(): void
{
// Retrieve all products from the database
$allProducts = Product::getAll();
Expand All @@ -33,7 +50,7 @@ private function getAllProducts(): void
/**
* Get the details of a specific product by its ID.
*/
private function getProductById(): void
public function getProductById(): void
{
$productId = (int)Utility::splitURL()[3];

Expand All @@ -55,7 +72,7 @@ private function getProductById(): void
/**
* Get the list of product categories.
*/
private function getProductCategories(): void
public function getProductCategories(): void
{
// Retrieve all product categories from the database
$categories = Product::getCategories();
Expand All @@ -67,7 +84,7 @@ private function getProductCategories(): void
/**
* Create a new product entry in the database.
*/
private function createProduct(): void
public function createProduct(): void
{
// Retrieve POST data
$postData = $_POST;
Expand Down Expand Up @@ -126,7 +143,7 @@ private function createProduct(): void
/**
* Delete a product with the specified ID.
*/
private function deleteProduct(): void
public function deleteProduct(): void
{
$productId = (int)Utility::splitURL()[3];

Expand Down Expand Up @@ -155,7 +172,7 @@ private function deleteProduct(): void
/**
* Update the details of a product with the specified ID.
*/
private function updateProduct(): void
public function updateProduct(): void
{
$productId = (int)Utility::splitURL()[3];

Expand Down Expand Up @@ -194,65 +211,4 @@ private function updateProduct(): void
echo json_encode(['error' => 'Failed to update product']);
}
}

private function getHandler($routes): ?string
{
foreach ($routes[$_SERVER['REQUEST_METHOD']] as $route => $handler) {
$pattern = str_replace('/', '\/', $route); // Convert to regex pattern
$pattern = preg_replace(
'/\{([a-zA-Z0-9_]+)\}/',
'(?P<$1>[^\/]+)',
$pattern
); // Replace placeholders with regex capture groups
$pattern = '/^' . $pattern . '$/';

if (preg_match($pattern, '/' . Utility::getURL(), $matches)) {
return $handler;
}
}
return null;
}

/**
* Main entry point for the Products API.
*/
public function index(): void
{
$routes = [
'GET' => [
'/api/v1/products' => 'getAllProducts',
'/api/v1/products/categories' => 'getProductCategories',
'/api/v1/products/{id}' => 'getProductById',
],
'POST' => [
'/api/v1/products' => 'createProduct',
],
'PUT' => [
'/api/v1/products/{id}' => 'updateProduct',
],
'DELETE' => [
'/api/v1/products/{id}' => 'deleteProduct',
]
];

// Handle the request
$handler = $this->getHandler($routes);

if ($handler !== null) {
$functionName = $handler;
if (method_exists($this, $functionName)) {
call_user_func(array($this, $functionName));
} else {
// Handle function not found
http_response_code(404);
echo "Function Not Found";
die();
}
} else {
// Handle route not found
http_response_code(404);
echo "Route Not Found";
die();
}
}
}
Loading

0 comments on commit 9fb7fae

Please sign in to comment.