Suas ideias em realidade digital!
Neste guia você vai montar uma API REST em PHP puro (sem framework) com:
/public
/api
index.php
.htaccess
/src
/Controllers
PostController.php
/Core
Router.php
Response.php
Auth.php
/Db
Database.php
Crie /public/api/.htaccess:
RewriteEngine On
# se arquivo ou pasta existe, entrega direto
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# tudo vai para o index.php
RewriteRule ^ index.php [QSA,L]
Crie /public/api/index.php:
<?php
header('Content-Type: application/json; charset=UTF-8');
require_once __DIR__ . '/../..//src/Core/Router.php';
require_once __DIR__ . '/../..//src/Core/Response.php';
require_once __DIR__ . '/../..//src/Core/Auth.php';
require_once __DIR__ . '/../..//src/Controllers/PostController.php';
$router = new Router();
// Health check
$router->get('/api/health', function() {
Response::json(['ok' => true, 'service' => 'makrosites-api']);
});
// Posts CRUD (exemplo)
$controller = new PostController();
$router->get('/api/posts', [$controller, 'index']);
$router->get('/api/posts/{id}', [$controller, 'show']);
// rotas protegidas
$router->post('/api/posts', function() use ($controller) {
Auth::requireBearer();
$controller->store();
});
$router->put('/api/posts/{id}', function($params) use ($controller) {
Auth::requireBearer();
$controller->update($params['id']);
});
$router->delete('/api/posts/{id}', function($params) use ($controller) {
Auth::requireBearer();
$controller->destroy($params['id']);
});
// executa
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
Crie /src/Core/Router.php:
<?php
class Router {
private array $routes = [];
public function get($path, $handler) { $this->add('GET', $path, $handler); }
public function post($path, $handler) { $this->add('POST', $path, $handler); }
public function put($path, $handler) { $this->add('PUT', $path, $handler); }
public function delete($path, $handler) { $this->add('DELETE', $path, $handler); }
private function add($method, $path, $handler) {
$this->routes[] = compact('method', 'path', 'handler');
}
public function dispatch($method, $uri) {
$path = parse_url($uri, PHP_URL_PATH);
foreach ($this->routes as $r) {
if ($r['method'] !== $method) continue;
$params = [];
$pattern = preg_replace('#\{([a-zA-Z_][a-zA-Z0-9_]*)\}#', '(?P<$1>[^/]+)', $r['path']);
$pattern = '#^' . $pattern . '$#';
if (preg_match($pattern, $path, $matches)) {
foreach ($matches as $k => $v) {
if (!is_int($k)) $params[$k] = $v;
}
// handler pode ser callable direto ou closure que recebe params
if (is_array($r['handler'])) {
return call_user_func($r['handler'], $params);
}
return $r['handler']($params);
}
}
Response::error('Not Found', 404);
}
}
Crie /src/Core/Response.php:
<?php
class Response {
public static function json($data, int $code = 200) {
http_response_code($code);
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
public static function error(string $message, int $code = 400, $details = null) {
$payload = ['error' => $message];
if ($details !== null) $payload['details'] = $details;
self::json($payload, $code);
}
public static function readJsonBody(): array {
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
if (!is_array($data)) self::error('JSON inválido', 422);
return $data;
}
}
Crie /src/Core/Auth.php:
<?php
class Auth {
public static function requireBearer() {
$hdr = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
// alguns servidores usam REDIRECT_HTTP_AUTHORIZATION
if (!$hdr && isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
$hdr = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
}
if (!preg_match('/Bearer\s+(.*)$/i', $hdr, $m)) {
Response::error('Unauthorized', 401);
}
$token = trim($m[1]);
// TODO: valide no banco
if ($token !== 'SEU_TOKEN_FIXO_DE_TESTE') {
Response::error('Token inválido', 401);
}
}
}
Crie /src/Controllers/PostController.php:
<?php
require_once __DIR__ . '/../Core/Response.php';
class PostController {
public function index() {
// exemplo estático (depois você liga no banco)
Response::json([
['id' => 1, 'title' => 'Post 1'],
['id' => 2, 'title' => 'Post 2'],
]);
}
public function show($params) {
$id = (int)($params['id'] ?? 0);
if ($id <= 0) Response::error('ID inválido', 422);
Response::json(['id' => $id, 'title' => 'Exemplo de post']);
}
public function store() {
$data = Response::readJsonBody();
if (empty($data['title'])) Response::error('title é obrigatório', 422);
// aqui você salva no banco...
Response::json(['message' => 'Criado com sucesso', 'data' => $data], 201);
}
public function update($id) {
$id = (int)$id;
if ($id <= 0) Response::error('ID inválido', 422);
$data = Response::readJsonBody();
Response::json(['message' => 'Atualizado', 'id' => $id, 'data' => $data]);
}
public function destroy($id) {
$id = (int)$id;
if ($id <= 0) Response::error('ID inválido', 422);
Response::json(['message' => 'Deletado', 'id' => $id]);
}
}
GET sem token:
curl -i https://www.makrosites.com.br/api/posts
POST com Bearer Token:
curl -i -X POST https://www.makrosites.com.br/api/posts \
-H "Authorization: Bearer SEU_TOKEN_FIXO_DE_TESTE" \
-H "Content-Type: application/json" \
-d '{"title":"Meu post"}'