ساخت Rest API برای اندروید با استفاده از PHP ، MySql و Slim – قسمت سوم

با سلام، خب در ادامه آموزش 

 

اول از همه پس از ساخت جداول در قسمت دوم از آموزش، ابتدا وارد هر جدول شده و مطمین شید که جداول درست ساخته شده باشند.

تا اینجای کار در دو قسمت قبلی از آموزش با مفاهیم پایه درمورد REST API و آماده سازی محیط توسعه آشنا شدیم، در این قسمت از آموزش یاد میگیریم که چگونه یه پروژه PHP رو آغاز کرده و کد برای REST API بنویسیم همچنین با تعدادی از کوئری های SQL برای عملیات CRUD نیز آشنا میشیم.

 

8- شروع پروژه PHP:

همانطور که می دانیم استفاده از IDE ها فرآیند توسعه رو تسهیل می کنند. پیشنهاد من به شما اینه که برای توسعه کد های PHP نیز از یک IDE  بجای استفاده از notepad استفاده کنید. از IDE های PHP میتوان  Eclipse ، Aptana Studio،  PhpStorm یا Netbeans را نام برد. که من خودم با PhpStorm (در کل عاشق ابزارهایی هستم که jetbrains ارائه میده) و Netbeans راحت هستم.

ساختار دایرکتوری پروژه PHP: 

دیاگرام ذیل شناختی در مورد ساختار پروژه به شما می دهد:

libs – تمام کتابخانه های ثالث در اینجا قرار می گیرند.

include – تمام کلاس های helper در اینجا هستند.

index.php – مراقبت از تمام درخواست های API در اینجا انجام می شود.

htaccess – قوانین برای ساختار url و باقی قوانین آپاچی (apache)

 

حال به شروع پروژه php می پردازیم:

 

  1. به دایرکتوری که WAMP را نصب کردیم می رویم (بهصورت پیش فرض در C:\wamp نصب می شود)
  2. برای قدم اول دایرکتوری های مورد نیازمان را میسازیم. درون فولدر wamp به فولدر www بروید (c:\wamp\www\)  و فولدری با نام task_manager بسازید. این فولدر به عنوان فولدر والد پروژه شناخته می شود. داخل task_manager سه فولدر دیگر با نام های libs و include و v1 بسازید.
  3. حال کتابخانه Slim را درون فولدر libs کپی کنید  (لینک دانلود Slim در قسمت قبلی آموزش هست)
  4. معمولا Slim framework زمانی که index.ph شامل url باشد که well-formed نیز باشد، به کار گرفته می شود. در نتیجه استفاده از قوانین .htacess میتوان فایل index.php را از url خلاص کرده و url های مورد علاقه خودمان را بسازیم. درون فولدر v1 فایلی با نام .htaccess بسازید و کد زیر را در آن کپی کنید (توجه کنید که این فایل هیچ extension نباید داشته باشد، مانند .txt  و … ) 

 

 

.htaccess
RewriteEngine On 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteRule ^(.*)$ %{ENV:BASE}index.php [QSA,L]

8-1 آماده سازی کلاس های Helper :

در ابتدا مجموعه ای از کلاس های helper که در پروژه استفاده می شوند را شروع به نوشتن می کنیم. این کلاس های helper برایمان function های مهمی را که برای تعامل با DB نیاز داریم فراهم می کنند.

5. درون فولدر include فایلی با نام Config.php با محتوی زیر بسازید، این فایل شامل کانفیگ کامل پروژه مانند اتصالات db و دیگر مقادیر می باشد.

Config.php
<?php
/**
 * Database configuration
 */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', 'localhost');
define('DB_NAME', 'task_manager');
 
define('USER_CREATED_SUCCESSFULLY', 0);
define('USER_CREATE_FAILED', 1);
define('USER_ALREADY_EXISTED', 2);
?>

6. کلاس دیگری با نام DbConnect.php بسازید، این کلاس در اصل مراقب اتصالات DB  می باشد.

DbConnect.php
<?php
 
/**
 * Handling database connection
 *
 * @author Ravi Tamada
 */
class DbConnect {
 
    private $conn;
 
    function __construct() {        
    }
 
    /**
     * Establishing database connection
     * @return database connection handler
     */
    function connect() {
        include_once dirname(__FILE__) . './Config.php';
 
        // Connecting to mysql database
        $this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
 
        // Check for database connection error
        if (mysqli_connect_errno()) {
            echo "Failed to connect to MySQL: " . mysqli_connect_error();
        }
 
        // returing connection resource
        return $this->conn;
    }
 
}
 
?>

encrypt کردن رمز عبور

7. بهترین روش برای ایمن کردن رمز عبور این است که آنها را به صورت متن خام ذخیره نکنیم، به جاش باید قبل از ذخیره تمام رمز عبور ها را encrypt نماییم. کلاس پیش رو برای encrypt کردن رمز عبور استفاده می شود. کلاس دیگری با نام PassHash.php ساخته و کد زیر را درآن کپی کنید.

PassHash.php
<?php
 
class PassHash {
 
    // blowfish
    private static $algo = '$2a';
    // cost parameter
    private static $cost = '$10';
 
    // mainly for internal use
    public static function unique_salt() {
        return substr(sha1(mt_rand()), 0, 22);
    }
 
    // this will be used to generate a hash
    public static function hash($password) {
 
        return crypt($password, self::$algo .
                self::$cost .
                '

8.حال کلاس دیگری با نام DbHandler.php می سازیم، این کلاس یکی از مهمترین فایل ها در پروژه می باشد که function های ضروری جهت انجام اعمال CRUD روی DB در آن قرار گرفته است. هر function توسط نامش و comment که دارد توضیحاتش داده شده است (توضیحات بیشتر در مورد function ها دراین آموزش نمیگنجه :) )

 

DbHandler.php
<?php
 
/**
 * Class to handle all db operations
 * This class will have CRUD methods for database tables
 *
 * @author Ravi Tamada
 */
class DbHandler {
 
    private $conn;
 
    function __construct() {
        require_once dirname(__FILE__) . './DbConnect.php';
        // opening db connection
        $db = new DbConnect();
        $this->conn = $db->connect();
    }
 
    /* ------------- `users` table method ------------------ */
 
    /**
     * Creating new user
     * @param String $name User full name
     * @param String $email User login email id
     * @param String $password User login password
     */
    public function createUser($name, $email, $password) {
        require_once 'PassHash.php';
        $response = array();
 
        // First check if user already existed in db
        if (!$this->isUserExists($email)) {
            // Generating password hash
            $password_hash = PassHash::hash($password);
 
            // Generating API key
            $api_key = $this->generateApiKey();
 
            // insert query
            $stmt = $this->conn->prepare("INSERT INTO users(name, email, password_hash, api_key, status) values(?, ?, ?, ?, 1)");
            $stmt->bind_param("ssss", $name, $email, $password_hash, $api_key);
 
            $result = $stmt->execute();
 
            $stmt->close();
 
            // Check for successful insertion
            if ($result) {
                // User successfully inserted
                return USER_CREATED_SUCCESSFULLY;
            } else {
                // Failed to create user
                return USER_CREATE_FAILED;
            }
        } else {
            // User with same email already existed in the db
            return USER_ALREADY_EXISTED;
        }
 
        return $response;
    }
 
    /**
     * Checking user login
     * @param String $email User login email id
     * @param String $password User login password
     * @return boolean User login status success/fail
     */
    public function checkLogin($email, $password) {
        // fetching user by email
        $stmt = $this->conn->prepare("SELECT password_hash FROM users WHERE email = ?");
 
        $stmt->bind_param("s", $email);
 
        $stmt->execute();
 
        $stmt->bind_result($password_hash);
 
        $stmt->store_result();
 
        if ($stmt->num_rows > 0) {
            // Found user with the email
            // Now verify the password
 
            $stmt->fetch();
 
            $stmt->close();
 
            if (PassHash::check_password($password_hash, $password)) {
                // User password is correct
                return TRUE;
            } else {
                // user password is incorrect
                return FALSE;
            }
        } else {
            $stmt->close();
 
            // user not existed with the email
            return FALSE;
        }
    }
 
    /**
     * Checking for duplicate user by email address
     * @param String $email email to check in db
     * @return boolean
     */
    private function isUserExists($email) {
        $stmt = $this->conn->prepare("SELECT id from users WHERE email = ?");
        $stmt->bind_param("s", $email);
        $stmt->execute();
        $stmt->store_result();
        $num_rows = $stmt->num_rows;
        $stmt->close();
        return $num_rows > 0;
    }
 
    /**
     * Fetching user by email
     * @param String $email User email id
     */
    public function getUserByEmail($email) {
        $stmt = $this->conn->prepare("SELECT name, email, api_key, status, created_at FROM users WHERE email = ?");
        $stmt->bind_param("s", $email);
        if ($stmt->execute()) {
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $user;
        } else {
            return NULL;
        }
    }
 
    /**
     * Fetching user api key
     * @param String $user_id user id primary key in user table
     */
    public function getApiKeyById($user_id) {
        $stmt = $this->conn->prepare("SELECT api_key FROM users WHERE id = ?");
        $stmt->bind_param("i", $user_id);
        if ($stmt->execute()) {
            $api_key = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $api_key;
        } else {
            return NULL;
        }
    }
 
    /**
     * Fetching user id by api key
     * @param String $api_key user api key
     */
    public function getUserId($api_key) {
        $stmt = $this->conn->prepare("SELECT id FROM users WHERE api_key = ?");
        $stmt->bind_param("s", $api_key);
        if ($stmt->execute()) {
            $user_id = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $user_id;
        } else {
            return NULL;
        }
    }
 
    /**
     * Validating user api key
     * If the api key is there in db, it is a valid key
     * @param String $api_key user api key
     * @return boolean
     */
    public function isValidApiKey($api_key) {
        $stmt = $this->conn->prepare("SELECT id from users WHERE api_key = ?");
        $stmt->bind_param("s", $api_key);
        $stmt->execute();
        $stmt->store_result();
        $num_rows = $stmt->num_rows;
        $stmt->close();
        return $num_rows > 0;
    }
 
    /**
     * Generating random Unique MD5 String for user Api key
     */
    private function generateApiKey() {
        return md5(uniqid(rand(), true));
    }
 
    /* ------------- `tasks` table method ------------------ */
 
    /**
     * Creating new task
     * @param String $user_id user id to whom task belongs to
     * @param String $task task text
     */
    public function createTask($user_id, $task) {        
        $stmt = $this->conn->prepare("INSERT INTO tasks(task) VALUES(?)");
        $stmt->bind_param("s", $task);
        $result = $stmt->execute();
        $stmt->close();
 
        if ($result) {
            // task row created
            // now assign the task to user
            $new_task_id = $this->conn->insert_id;
            $res = $this->createUserTask($user_id, $new_task_id);
            if ($res) {
                // task created successfully
                return $new_task_id;
            } else {
                // task failed to create
                return NULL;
            }
        } else {
            // task failed to create
            return NULL;
        }
    }
 
    /**
     * Fetching single task
     * @param String $task_id id of the task
     */
    public function getTask($task_id, $user_id) {
        $stmt = $this->conn->prepare("SELECT t.id, t.task, t.status, t.created_at from tasks t, user_tasks ut WHERE t.id = ? AND ut.task_id = t.id AND ut.user_id = ?");
        $stmt->bind_param("ii", $task_id, $user_id);
        if ($stmt->execute()) {
            $task = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $task;
        } else {
            return NULL;
        }
    }
 
    /**
     * Fetching all user tasks
     * @param String $user_id id of the user
     */
    public function getAllUserTasks($user_id) {
        $stmt = $this->conn->prepare("SELECT t.* FROM tasks t, user_tasks ut WHERE t.id = ut.task_id AND ut.user_id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        $tasks = $stmt->get_result();
        $stmt->close();
        return $tasks;
    }
 
    /**
     * Updating task
     * @param String $task_id id of the task
     * @param String $task task text
     * @param String $status task status
     */
    public function updateTask($user_id, $task_id, $task, $status) {
        $stmt = $this->conn->prepare("UPDATE tasks t, user_tasks ut set t.task = ?, t.status = ? WHERE t.id = ? AND t.id = ut.task_id AND ut.user_id = ?");
        $stmt->bind_param("siii", $task, $status, $task_id, $user_id);
        $stmt->execute();
        $num_affected_rows = $stmt->affected_rows;
        $stmt->close();
        return $num_affected_rows > 0;
    }
 
    /**
     * Deleting a task
     * @param String $task_id id of the task to delete
     */
    public function deleteTask($user_id, $task_id) {
        $stmt = $this->conn->prepare("DELETE t FROM tasks t, user_tasks ut WHERE t.id = ? AND ut.task_id = t.id AND ut.user_id = ?");
        $stmt->bind_param("ii", $task_id, $user_id);
        $stmt->execute();
        $num_affected_rows = $stmt->affected_rows;
        $stmt->close();
        return $num_affected_rows > 0;
    }
 
    /* ------------- `user_tasks` table method ------------------ */
 
    /**
     * Function to assign a task to user
     * @param String $user_id id of the user
     * @param String $task_id id of the task
     */
    public function createUserTask($user_id, $task_id) {
        $stmt = $this->conn->prepare("INSERT INTO user_tasks(user_id, task_id) values(?, ?)");
        $stmt->bind_param("ii", $user_id, $task_id);
        $result = $stmt->execute();
        $stmt->close();
        return $result;
    }
 
}
 
?>

8.2 بررسی فراخوانی های API 

تا اینجا کلسا های مورد نیاز برای REST APIرا داریم. حال میتونیم شرع به کدنویسی برای بررسی api calls ها بکنیم.

8. درون فولدر v1 فایلی با نام index.php بسازید و کد زیر را در آن کپی کنید. در اینجا کتابخانه های مورد نیاز و دیگر helper function ها را include می کنیم.

verifyRequiredParams() - این فانکشن پارامتر های الزامی در درخواست را تایید می کند.

validateEmail() - تایید می کند که آدرس ایمیل معتبر هست یا خیر.

echoRespnse() - این فانکشن پاسخ json با کد وضعیت (status code) را منعکس می کند.

index.php
<?php
 
require_once '../include/DbHandler.php';
require_once '../include/PassHash.php';
require '.././libs/Slim/Slim.php';
 
\Slim\Slim::registerAutoloader();
 
$app = new \Slim\Slim();
 
// User id from db - Global Variable
$user_id = NULL;
 
/**
 * Verifying required params posted or not
 */
function verifyRequiredParams($required_fields) {
    $error = false;
    $error_fields = "";
    $request_params = array();
    $request_params = $_REQUEST;
    // Handling PUT request params
    if ($_SERVER['REQUEST_METHOD'] == 'PUT') {
        $app = \Slim\Slim::getInstance();
        parse_str($app->request()->getBody(), $request_params);
    }
    foreach ($required_fields as $field) {
        if (!isset($request_params[$field]) || strlen(trim($request_params[$field])) <= 0) {
            $error = true;
            $error_fields .= $field . ', ';
        }
    }
 
    if ($error) {
        // Required field(s) are missing or empty
        // echo error json and stop the app
        $response = array();
        $app = \Slim\Slim::getInstance();
        $response["error"] = true;
        $response["message"] = 'Required field(s) ' . substr($error_fields, 0, -2) . ' is missing or empty';
        echoRespnse(400, $response);
        $app->stop();
    }
}
 
/**
 * Validating email address
 */
function validateEmail($email) {
    $app = \Slim\Slim::getInstance();
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $response["error"] = true;
        $response["message"] = 'Email address is not valid';
        echoRespnse(400, $response);
        $app->stop();
    }
}
 
/**
 * Echoing json response to client
 * @param String $status_code Http response code
 * @param Int $response Json response
 */
function echoRespnse($status_code, $response) {
    $app = \Slim\Slim::getInstance();
    // Http response code
    $app->status($status_code);
 
    // setting response content type to json
    $app->contentType('application/json');
 
    echo json_encode($response);
}
 
$app->run();
?>

پاسخ های JSON  یا Jason Response:

در فراخوانی هر  API، یک درخوسات JSON response با یه کد وضعیت HTTP صادر خواهد شد. سمت کلاینت باید کد وضعیت http پاسخ تایید شود. اگر وضعیت 200 باشد، درخواست با موفقیت انجام شده است. همچنین باید به گره error در پاسخ توجه شود. اگر مقدار error صحیح (true) باشد، به این معنی است که تعدای خطا د رزمان پردازش داده های کاربر رخ داده است.

 

API calls بدون احراز هویت (درون header درخواست API Key ندارند)

این فراحوانی ها درون request header شان API Key ندارند. هدف اصلی از این نوع درخواست ها تعامل با پایگاه داده بدون هیچگونه احراز هویتی می باشد. ثبت نام و لاگین کردن کاربر در این دسته قرار می گیرد.

 

در قسمت بعدی به تکمیل  API Calls ها پرداخته خواهد شد...

قسمت اول آموزش: لینک

قسمت دوم آموزش: لینک 

قسمت چهارم آموزش: لینک

 

 

. self::unique_salt());
}

// this will be used to compare a password against a hash
public static function check_password($hash, $password) {
$full_salt = substr($hash, 0, 29);
$new_hash = crypt($password, $full_salt);
return ($hash == $new_hash);
}

}

?>
8.حال کلاس دیگری با نام DbHandler.php می سازیم، این کلاس یکی از مهمترین فایل ها در پروژه می باشد که function های ضروری جهت انجام اعمال CRUD روی DB در آن قرار گرفته است. هر function توسط نامش و comment که دارد توضیحاتش داده شده است (توضیحات بیشتر در مورد function ها دراین آموزش نمیگنجه 🙂 )

 


8.2 بررسی فراخوانی های API 

تا اینجا کلسا های مورد نیاز برای REST APIرا داریم. حال میتونیم شرع به کدنویسی برای بررسی api calls ها بکنیم.

8. درون فولدر v1 فایلی با نام index.php بسازید و کد زیر را در آن کپی کنید. در اینجا کتابخانه های مورد نیاز و دیگر helper function ها را include می کنیم.

verifyRequiredParams() – این فانکشن پارامتر های الزامی در درخواست را تایید می کند.

validateEmail() – تایید می کند که آدرس ایمیل معتبر هست یا خیر.

echoRespnse() – این فانکشن پاسخ json با کد وضعیت (status code) را منعکس می کند.


پاسخ های JSON  یا Jason Response:

در فراخوانی هر  API، یک درخوسات JSON response با یه کد وضعیت HTTP صادر خواهد شد. سمت کلاینت باید کد وضعیت http پاسخ تایید شود. اگر وضعیت 200 باشد، درخواست با موفقیت انجام شده است. همچنین باید به گره error در پاسخ توجه شود. اگر مقدار error صحیح (true) باشد، به این معنی است که تعدای خطا د رزمان پردازش داده های کاربر رخ داده است.

 

API calls بدون احراز هویت (درون header درخواست API Key ندارند)

این فراحوانی ها درون request header شان API Key ندارند. هدف اصلی از این نوع درخواست ها تعامل با پایگاه داده بدون هیچگونه احراز هویتی می باشد. ثبت نام و لاگین کردن کاربر در این دسته قرار می گیرد.

 

در قسمت بعدی به تکمیل  API Calls ها پرداخته خواهد شد…

قسمت اول آموزش: لینک

قسمت دوم آموزش: لینک 

قسمت چهارم آموزش: لینک