├── .gitignore ├── .github ├── api.png ├── miniatura-v2.png └── screenshot.png ├── api ├── composer.json ├── .htaccess ├── index.php ├── src │ ├── Controllers │ │ ├── HomeController.php │ │ ├── NotFoundController.php │ │ └── UserController.php │ ├── Models │ │ ├── Database.php │ │ └── User.php │ ├── Http │ │ ├── Response.php │ │ ├── Request.php │ │ ├── Route.php │ │ └── JWT.php │ ├── Utils │ │ └── Validator.php │ ├── routes │ │ └── main.php │ ├── Core │ │ └── Core.php │ └── Services │ │ └── UserService.php └── composer.lock ├── LICENSE └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ -------------------------------------------------------------------------------- /.github/api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricNeves/yt-api-com-php/HEAD/.github/api.png -------------------------------------------------------------------------------- /.github/miniatura-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricNeves/yt-api-com-php/HEAD/.github/miniatura-v2.png -------------------------------------------------------------------------------- /.github/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricNeves/yt-api-com-php/HEAD/.github/screenshot.png -------------------------------------------------------------------------------- /api/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoload": { 3 | "psr-4": { 4 | "App\\": "src/" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /api/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteCond %{REQUEST_FILENAME} !-f 5 | 6 | RewriteRule ^(.*)$ index.php?url=$1 [QSA] -------------------------------------------------------------------------------- /api/index.php: -------------------------------------------------------------------------------- 1 | $value) { 10 | if (empty(trim($value))) { 11 | throw new \Exception("The field ($field) is required."); 12 | } 13 | } 14 | 15 | return $fields; 16 | } 17 | } -------------------------------------------------------------------------------- /api/src/routes/main.php: -------------------------------------------------------------------------------- 1 | true, 14 | 'success' => false, 15 | 'message' => 'Sorry, route not found.' 16 | ], 404); 17 | return; 18 | } 19 | } -------------------------------------------------------------------------------- /api/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "d751713988987e9331980363e24189ce", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": [], 16 | "platform-dev": [], 17 | "plugin-api-version": "2.2.0" 18 | } 19 | -------------------------------------------------------------------------------- /api/src/Http/Request.php: -------------------------------------------------------------------------------- 1 | $_GET, 18 | 'POST', 'PUT', 'DELETE' => $json, 19 | }; 20 | 21 | return $data; 22 | } 23 | 24 | public static function authorization() 25 | { 26 | $authorization = getallheaders(); 27 | 28 | if (!isset($authorization['Authorization'])) return ['error' => 'Sorry, no authorization header provided']; 29 | 30 | $authorizationPartials = explode(' ', $authorization['Authorization']); 31 | 32 | if (count($authorizationPartials) != 2) return ['error'=> 'Please, provide a valid authorization header.']; 33 | 34 | return $authorizationPartials[1] ?? ''; 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Eric Neves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/src/Http/Route.php: -------------------------------------------------------------------------------- 1 | $path, 13 | 'action' => $action, 14 | 'method' => 'GET' 15 | ]; 16 | } 17 | 18 | public static function post(string $path, string $action) 19 | { 20 | self::$routes[] = [ 21 | 'path' => $path, 22 | 'action' => $action, 23 | 'method' => 'POST' 24 | ]; 25 | } 26 | 27 | public static function put(string $path, string $action) 28 | { 29 | self::$routes[] = [ 30 | 'path' => $path, 31 | 'action' => $action, 32 | 'method' => 'PUT' 33 | ]; 34 | } 35 | 36 | public static function delete(string $path, string $action) 37 | { 38 | self::$routes[] = [ 39 | 'path' => $path, 40 | 'action' => $action, 41 | 'method' => 'DELETE' 42 | ]; 43 | } 44 | 45 | public static function routes() 46 | { 47 | return self::$routes; 48 | } 49 | } -------------------------------------------------------------------------------- /api/src/Core/Core.php: -------------------------------------------------------------------------------- 1 | true, 33 | 'success' => false, 34 | 'message' => 'Sorry, method not allowed.' 35 | ], 405); 36 | return; 37 | } 38 | 39 | [$controller, $action] = explode('@', $route['action']); 40 | 41 | $controller = $prefixController . $controller; 42 | $extendController = new $controller(); 43 | $extendController->$action(new Request, new Response, $matches); 44 | } 45 | } 46 | 47 | if (!$routeFound) { 48 | $controller = $prefixController . 'NotFoundController'; 49 | $extendController = new $controller(); 50 | $extendController->index(new Request, new Response); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /api/src/Http/JWT.php: -------------------------------------------------------------------------------- 1 | 'JWT', 'alg' => 'HS256']); 12 | $payload = json_encode($data); 13 | 14 | $base64UrlHeader = self::base64url_encode($header); 15 | $base64UrlPayload = self::base64url_encode($payload); 16 | 17 | $signature = self::signature($base64UrlHeader, $base64UrlPayload); 18 | 19 | $jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $signature; 20 | 21 | return $jwt; 22 | } 23 | 24 | public static function verify(string $jwt) 25 | { 26 | $tokenPartials = explode('.', $jwt); 27 | 28 | if (count($tokenPartials) != 3) return false; 29 | 30 | [$header, $payload, $signature] = $tokenPartials; 31 | 32 | if ($signature !== self::signature($header, $payload)) return false; 33 | 34 | return self::base64url_decode($payload); 35 | } 36 | 37 | public static function signature(string $header, string $payload) 38 | { 39 | $signature = hash_hmac('sha256', $header . "." . $payload, self::$secret, true); 40 | 41 | return self::base64url_encode($signature); 42 | } 43 | 44 | public static function base64url_encode($data) 45 | { 46 | return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 47 | } 48 | 49 | public static function base64url_decode($data) 50 | { 51 | $padding = strlen($data) % 4; 52 | 53 | $padding !== 0 && $data .= str_repeat('=', 4 - $padding); 54 | 55 | $data = strtr($data, '-_', '+/'); 56 | 57 | return json_decode(base64_decode($data), true); 58 | } 59 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | 4 |
5 | API com PHP 6 |
7 |

8 | 9 |

10 | Este repositório contém o código-fonte de uma API desenvolvida em PHP puro. A API é demonstrada em um vídeo tutorial disponível no YouTube, criado pelo autor deste repositório. 11 |

12 |

13 | O vídeo tutorial associado a este repositório pode ser acessado aqui. 🚀 14 |

15 | 16 |

17 | Se gostou, deixe sua 🌟 no projeto! 18 |

19 | 20 | ![screenshot](.github/miniatura-v2.png) 21 | 22 | ### Descrição 23 | 24 | A API em PHP puro foi desenvolvida como parte de um tutorial prático, projetado para ensinar os conceitos fundamentais de criação de APIs usando PHP. No vídeo tutorial associado a este repositório, você aprenderá a construir uma API simples, utilizando apenas PHP e algumas práticas recomendadas. 25 | 26 | ### Vídeo Tutorial 27 | 28 | O vídeo tutorial associado a este repositório pode ser acessado aqui. 29 | 30 | Principais Recursos: 31 | * PSR-4 32 | * HTTP 33 | * Request 34 | * Response 35 | * HTTP Method 36 | * Autenticação por JWT 37 | * Rotas 38 | * Banco de Dados 39 | * CRUD (Create, Read, Update, Delete) 40 | 41 | ### Execução 42 | 43 | ```sh 44 | 45 | # Clone Repository 46 | $ git clone https://github.com/EricNeves/yt-api-com-php.git 47 | 48 | # Folder 49 | $ cp yt-api-com-php/ /var/www/html 50 | 51 | # Install Dependencies - PHP 52 | $ cd yt-api-com-php/ && composer update 53 | 54 | ``` 55 | 56 | Mova o projeto para dentro do seu servidor Apache ou NGINX: 57 | 58 | * API: http://localhost/yt-api-com-php/ 59 | 60 | ### License 61 | 62 | 63 | 64 | ### Author 🧑‍💻 65 | 66 | -------------------------------------------------------------------------------- /api/src/Models/User.php: -------------------------------------------------------------------------------- 1 | prepare(" 15 | INSERT 16 | INTO 17 | users (name, email, password) 18 | VALUES 19 | (?, ?, ?) 20 | "); 21 | 22 | $stmt->execute([ 23 | $data['name'], 24 | $data['email'], 25 | $data['password'], 26 | ]); 27 | 28 | return $pdo->lastInsertId() > 0 ? true : false; 29 | } 30 | 31 | public static function authentication(array $data) 32 | { 33 | $pdo = self::getConnection(); 34 | 35 | $stmt = $pdo->prepare(" 36 | SELECT 37 | * 38 | FROM 39 | users 40 | WHERE 41 | email = ? 42 | "); 43 | 44 | $stmt->execute([$data['email']]); 45 | 46 | if ($stmt->rowCount() < 1) return false; 47 | 48 | $user = $stmt->fetch(PDO::FETCH_ASSOC); 49 | 50 | if (!password_verify($data['password'], $user['password'])) return false; 51 | 52 | return [ 53 | 'id' => $user['id'], 54 | 'name' => $user['name'], 55 | 'email'=> $user['email'], 56 | ]; 57 | } 58 | 59 | public static function find(int|string $id) 60 | { 61 | $pdo = self::getConnection(); 62 | 63 | $stmt = $pdo->prepare(' 64 | SELECT 65 | id, name, email 66 | FROM 67 | users 68 | WHERE 69 | id = ? 70 | '); 71 | 72 | $stmt->execute([$id]); 73 | 74 | return $stmt->fetch(PDO::FETCH_ASSOC); 75 | } 76 | 77 | public static function update(int|string $id, array $data) 78 | { 79 | $pdo = self::getConnection(); 80 | 81 | $stmt = $pdo->prepare(' 82 | UPDATE 83 | users 84 | SET 85 | name = ? 86 | WHERE 87 | id = ? 88 | '); 89 | 90 | $stmt->execute([$data['name'], $id]); 91 | 92 | return $stmt->rowCount() > 0 ? true : false; 93 | } 94 | 95 | public static function delete(int|string $id) 96 | { 97 | $pdo = self::getConnection(); 98 | 99 | $stmt = $pdo->prepare(' 100 | DELETE 101 | FROM 102 | users 103 | WHERE 104 | id = ? 105 | '); 106 | 107 | $stmt->execute([$id]); 108 | 109 | return $stmt->rowCount() > 0 ? true : false; 110 | } 111 | } -------------------------------------------------------------------------------- /api/src/Controllers/UserController.php: -------------------------------------------------------------------------------- 1 | true, 20 | 'success' => false, 21 | 'message' => $userService['error'] 22 | ], 400); 23 | } 24 | 25 | $response::json([ 26 | 'error' => false, 27 | 'success' => true, 28 | 'data' => $userService 29 | ], 201); 30 | } 31 | 32 | public function login(Request $request, Response $response) 33 | { 34 | $body = $request::body(); 35 | 36 | $userService = UserService::auth($body); 37 | 38 | if (isset($userService['error'])) { 39 | return $response::json([ 40 | 'error' => true, 41 | 'success' => false, 42 | 'message' => $userService['error'] 43 | ], 400); 44 | } 45 | 46 | $response::json([ 47 | 'error' => false, 48 | 'success' => true, 49 | 'jwt' => $userService 50 | ], 200); 51 | return; 52 | } 53 | 54 | public function fetch(Request $request, Response $response) 55 | { 56 | $authorization = $request::authorization(); 57 | 58 | $userService = UserService::fetch($authorization); 59 | 60 | if (isset($userService['unauthorized'])) { 61 | return $response::json([ 62 | 'error' => true, 63 | 'success' => false, 64 | 'message' => $userService['unauthorized'] 65 | ], 401); 66 | } 67 | 68 | if (isset($userService['error'])) { 69 | return $response::json([ 70 | 'error' => true, 71 | 'success' => false, 72 | 'message' => $userService['error'] 73 | ], 400); 74 | } 75 | 76 | $response::json([ 77 | 'error' => false, 78 | 'success' => true, 79 | 'data' => $userService 80 | ], 200); 81 | return; 82 | } 83 | 84 | public function update(Request $request, Response $response) 85 | { 86 | $authorization = $request::authorization(); 87 | 88 | $body = $request::body(); 89 | 90 | $userService = UserService::update($authorization, $body); 91 | 92 | if (isset($userService['unauthorized'])) { 93 | return $response::json([ 94 | 'error' => true, 95 | 'success' => false, 96 | 'message' => $userService['unauthorized'] 97 | ], 401); 98 | } 99 | 100 | if (isset($userService['error'])) { 101 | return $response::json([ 102 | 'error' => true, 103 | 'success' => false, 104 | 'message' => $userService['error'] 105 | ], 400); 106 | } 107 | 108 | $response::json([ 109 | 'error' => false, 110 | 'success' => true, 111 | 'message' => $userService 112 | ], 200); 113 | return; 114 | } 115 | 116 | public function remove(Request $request, Response $response, array $id) 117 | { 118 | $authorization = $request::authorization(); 119 | 120 | $userService = UserService::delete($authorization, $id[0]); 121 | 122 | if (isset($userService['unauthorized'])) { 123 | return $response::json([ 124 | 'error' => true, 125 | 'success' => false, 126 | 'message' => $userService['unauthorized'] 127 | ], 401); 128 | } 129 | 130 | if (isset($userService['error'])) { 131 | return $response::json([ 132 | 'error' => true, 133 | 'success' => false, 134 | 'message' => $userService['error'] 135 | ], 400); 136 | } 137 | 138 | $response::json([ 139 | 'error' => false, 140 | 'success' => true, 141 | 'message' => $userService 142 | ], 200); 143 | return; 144 | } 145 | } -------------------------------------------------------------------------------- /api/src/Services/UserService.php: -------------------------------------------------------------------------------- 1 | $data['name'] ?? '', 18 | 'email' => $data['email'] ?? '', 19 | 'password' => $data['password'] ?? '', 20 | ]); 21 | 22 | $fields['password'] = password_hash($fields['password'], PASSWORD_DEFAULT); 23 | 24 | $user = User::save($fields); 25 | 26 | if (!$user) return ['error' => 'Sorry, we could not create your account.']; 27 | 28 | return "User created successfully!"; 29 | 30 | } 31 | catch (PDOException $e) { 32 | if ($e->errorInfo[0] === '08006') return ['error' => 'Sorry, we could not connect to the database.']; 33 | if ($e->errorInfo[0] === '23505') return ['error' => 'Sorry, user already exists.']; 34 | return ['error' => $e->errorInfo[0]]; 35 | } 36 | catch (Exception $e) { 37 | return ['error' => $e->getMessage()]; 38 | } 39 | } 40 | 41 | public static function auth(array $data) 42 | { 43 | try { 44 | $fields = Validator::validate([ 45 | 'email' => $data['email'] ?? '', 46 | 'password' => $data['password'] ?? '', 47 | ]); 48 | 49 | $user = User::authentication($fields); 50 | 51 | if (!$user) return ['error'=> 'Sorry, we could not authenticate you.']; 52 | 53 | return JWT::generate($user); 54 | } 55 | catch (PDOException $e) { 56 | if ($e->errorInfo[0] === '08006') return ['error' => 'Sorry, we could not connect to the database.']; 57 | return ['error' => $e->errorInfo[0]]; 58 | } 59 | catch (Exception $e) { 60 | return ['error' => $e->getMessage()]; 61 | } 62 | } 63 | 64 | public static function fetch(mixed $authorization) 65 | { 66 | try { 67 | if (isset($authorization['error'])) { 68 | return ['unauthorized'=> $authorization['error']]; 69 | } 70 | 71 | $userFromJWT = JWT::verify($authorization); 72 | 73 | if (!$userFromJWT) return ['unauthorized'=> "Please, login to access this resource."]; 74 | 75 | $user = User::find($userFromJWT['id']); 76 | 77 | if (!$user) return ['error'=> 'Sorry, we could not find your account.']; 78 | 79 | return $user; 80 | } 81 | catch (PDOException $e) { 82 | if ($e->errorInfo[0] === '08006') return ['error' => 'Sorry, we could not connect to the database.']; 83 | return ['error' => $e->errorInfo[0]]; 84 | } 85 | catch (Exception $e) { 86 | return ['error' => $e->getMessage()]; 87 | } 88 | } 89 | 90 | public static function update(mixed $authorization, array $data) 91 | { 92 | try { 93 | if (isset($authorization['error'])) { 94 | return ['unauthorized'=> $authorization['error']]; 95 | } 96 | 97 | $userFromJWT = JWT::verify($authorization); 98 | 99 | if (!$userFromJWT) return ['unauthorized'=> "Please, login to access this resource."]; 100 | 101 | $fields = Validator::validate([ 102 | 'name' => $data['name'] ?? '' 103 | ]); 104 | 105 | $user = User::update($userFromJWT['id'], $fields); 106 | 107 | if (!$user) return ['error'=> 'Sorry, we could not update your account.']; 108 | 109 | return "User updated successfully!"; 110 | } 111 | catch (PDOException $e) { 112 | if ($e->errorInfo[0] === '08006') return ['error' => 'Sorry, we could not connect to the database.']; 113 | return ['error' => $e->getMessage()]; 114 | } 115 | catch (Exception $e) { 116 | return ['error' => $e->getMessage()]; 117 | } 118 | } 119 | 120 | public static function delete(mixed $authorization, int|string $id) 121 | { 122 | try { 123 | if (isset($authorization['error'])) { 124 | return ['unauthorized'=> $authorization['error']]; 125 | } 126 | 127 | $userFromJWT = JWT::verify($authorization); 128 | 129 | if (!$userFromJWT) return ['unauthorized'=> "Please, login to access this resource."]; 130 | 131 | $user = User::delete($id); 132 | 133 | if (!$user) return ['error'=> 'Sorry, we could not delete your account.']; 134 | 135 | return "User deleted successfully!"; 136 | } 137 | catch (PDOException $e) { 138 | if ($e->errorInfo[0] === '08006') return ['error' => 'Sorry, we could not connect to the database.']; 139 | return ['error' => $e->getMessage()]; 140 | } 141 | catch (Exception $e) { 142 | return ['error' => $e->getMessage()]; 143 | } 144 | } 145 | } --------------------------------------------------------------------------------