چگونه در PHP 7 بدون استفاده از فریمورک به صورت مدرن کد بزنیم – بخش اول

چگونه در PHP 7 بدون استفاده از فریمورک به صورت مدرن کد بزنیم – بخش اول
آفلاین
user-avatar
عرفان حشمتی
02 دی 1399, خواندن در 8 دقیقه

از زمان معرفی پکیج کامپوزر و استانداردهای PHP، نوشتن کد با PHP آسان‌تر و قابل کنترل‌تر شد. در حالی که قبلا مجبور می‌شدید از یک فریمورک برای حفظ پروژه خود در یک زمینه حرفه‌ای استفاده کنید. امروزه این کار ضروری نیست و به شما نشان می‌دهیم چگونه یک پروژه کوچک API با مسیریابی اساسی، پکیج‌های شخص ثالث و تست بدون فریمورک را انجام دهید.

چند دلیل وجود دارد که نمی‌خواهید از فریمورک استفاده کنید. وقتی شما یک کتابخانه یا یک برنامه کوچک ایجاد می‌کنید، کنترل بیشتری دارید و بسته به مواردی خاص ممکن است بخواهید از یک فریمورک استفاده کنید.

در اینجا هدف ما ایجاد یک Blog Api ساده است. هر پست دارای شناسه، عنوان و متن خواهد بود. شما می‌توانید یک پست را لیست کنید، ایجاد کنید و همچنین مشاهده کنید. ما از هیچ پایگاه داده‌ای استفاده نخواهیم کرد و یک فایل JSON ساده به عنوان دیتابیس باید کافی باشد که همه درخواست‌ها و پاسخ‌ها در قالب JSON خواهد بود.

همانطور که می‌بینید برخی از ویژگی‌ها وجود ندارد مانند خلاصه، تاریخ انتشار، نویسنده، برچسب‌ها، دسته‌ها و موارد دیگر و یا قابلیت حذف و به روزرسانی. تصمیم بر این شد که آن‌ها را اجرا نکنیم و فقط برخی از کلاس‌ها و کدها را بدون جزئیات برای کوتاه کردن این مقاله به طور خلاصه توضیح خواهیم داد. اگر به توضیحات بیشتری در مورد هر مرحله نیاز دارید، لطفا آن را در نظرات بگذارید و ما تمام تلاش خود را برای کمک به شما خواهیم کرد.

کد پروژه در این لینک موجود است.

پیش نیازها

  • PHP 7.0 یا نسخه بالاتر
  • کامپوزر
  • دانش پایه پی اچ پی، کلاس‌ها، کامپوزر، الگوی MVC

نصب کامپوزر

اولین کاری که باید انجام دهیم این است که کامپوزر خود را ایجاد کنیم. json برای اضافه کردن پکیج‌های شخص ثالث و مدیریت پروژه به ویژگی بارگیری خودکار نیاز دارد، این امر ورود کلاس‌ها را آسان می‌کند.

یک پوشه ایجاد کنید و کامپوزر را در ترمینال خود تایپ کنید و اطلاعات را پر کنید. این کار باعث ایجاد فایل composer.json برای ما می‌شود، سپس در مسیر پوشه اصلی فایل‌های خالی به نامindex.php ، config.php و یک پوشه خالی به نام App ایجاد کنید.

بیایید اولین پکیج را با استفاده از کامپوزر خط فرمان که نیازمند monolog/monolog:1.25.1 است، اضافه کنیم. یک پوشه vendor با پکیجی که اضافه کردیم و یک فایل به نام autoload.php ایجاد می‌کند، این فایل شامل تمام مسیر کلاس‌هایی است که ما داریم استفاده می‌کنیم. monolog یک پکیج برای ایجاد فایل‌های گزارش است که بعدا مورد استفاده قرار می‌گیرد.

index.php را باز کنید و کدهای زیر را وارد کنید:

<?php
require __DIR__ . '/vendor/autoload.php';

با افزودن ورودی autoload پس از ورود تایپ، composer.json را اصلاح کنید.

"type": "project",
"autoload": {
  "psr-4": {
    "App\\": "App/"
   }
},

سپس برای به روزرسانی ورودی‌ها، کامپوزر dump-autoload را تایپ کنید. ورودی autoload تمام کلاس‌های ما را ثبت می‌کند تا در هر نقطه از برنامه ما استفاده شود. psr-4 انعطاف‌پذیر تر از مشخصات استاندارد بارگیری خودکار psr-0 است، شما نیازی به بازسازی autoloader هنگام اضافه کردن کلاس‌ها ندارید.

در حال حاضر برنامه از قبل برای کار با کامپوزر تنظیم شده است. شما می‌توانید php index.php را در ترمینال خود اجرا کنید، اگر هیچ خطایی نشان داده نشد پس کار می‌کند، این نباید خروجی داشته باشد.

اضافه کردن کلاس اول

بیایید یک راهنمای پیکربندی برای استفاده در کل پروژه ایجاد کنیم. ما می‌خواهیم دو فایل در روت پروژه داشته باشیم یکی config.php و دیگری تنظیمات اولیه برای برنامه. اینجا جایی است که کلید API، تنظیم Cache و غیره را قرار می‌دهید و باید یک اصول متفاوت در محیط خود داشته باشید (تست، اجرا، تولید) و فایل دیگر App / Lib / Config.php برای خواندن این متغیرها خواهد بود.

config.php را باز کنید و موارد زیر را در آن وارد کنید:

<?php
return [
 'LOG_PATH' => __DIR__ . './logs',
];

یک فایل جدید در داخل App / Lib ایجاد کنید و آن را Config.php بنامید و این کد را جایگذاری کنید:

App/Lib/Config.php

<?php namespace App\Lib;

class Config
{
    private static $config;

    public static function get($key, $default = null)
    {
        if (is_null(self::$config)) {
            self::$config = require_once(__DIR__.'/../../config.php');
        }

        return !empty(self::$config[$key])?self::$config[$key]:$default;
    }
}

این کد آرایه را از config.php می‌خواند و بررسی می‌کند که آیا کلید در آرایه وجود دارد یا نه. اگر وجود داشت مقدار را برمی‌گرداند در غیر این صورت مقدار پیش فرض داده شده را برمی‌گرداند.

بیایید بررسی کنیم که آیا ویرایش index.php با اضافه کردن این خطوط کار می‌کند یا خیر.

<?php require __DIR__ . '/vendor/autoload.php';

// New lines
use App\Lib\Config;
$LOG_PATH = Config::get('LOG_PATH', '');
echo "[LOG_PATH]: $LOG_PATH";

اکنون php index.php را اجرا کنید و باید مسیر گزارش‌های مشخص شده در config.php را به خروجی بفرستد.

به نظر می‌رسد زیاد نیست. اما در این مرحله شما باید در مورد نحوه کار بقیه کد ایده بگیرید. ما برخی از کلاس‌ها را به پوشه برنامه اضافه می‌کنیم و به لطف autoload در هر کجای برنامه قابل دسترسی است.

اضافه کردن Logging

پیش از این ما پکیج monolog را به وابستگی‌های خود اضافه کردیم، این پکیج شامل مجموعه‌ای از کلاس‌ها و مولفه‌ها برای مدیریت گزارش‌ها است. ورود به سیستم یک قسمت ضروری در هر برنامه است، زیرا اولین چیزی است که در صورت بروز هرگونه مشکل بررسی می‌شود و پکیج‌هایی مانند monolog این کار را آسان‌تر می‌کنند. برای این برنامه، می‌خواهیم سه فایل ورود به سیستم ساده شاملError.log ، Applications.log و app.log ایجاد کنیم.

گزارش‌های مربوط به خطاها و درخواست‌ها فعال خواهند بود و از گزارش‌های برنامه در صورت تقاضا برای نمایش اطلاعات مربوط به درخواست ما استفاده می‌شود. گزارش ارورها شامل هر خطایی است که در برنامه رخ می‌دهد و requests.log هرگونه درخواست HTTP ایجاد شده در برنامه را ثبت خواهد کرد.

فایل logger.php را در مسیر App/Lib ایجاد کنید و کد زیر را در آن وارد کنید، این شامل محتوایی خواهد بود که گزارش های مختلف را مدیریت خواهد کرد.

App/Lib/Logger.php

<?php namespace App\Lib;

use Monolog\ErrorHandler;
use Monolog\Handler\StreamHandler;

class Logger extends \Monolog\Logger
{
    private static $loggers = [];

    public function __construct($key = "app", $config = null)
    {
        parent::__construct($key);

        if (empty($config)) {
            $LOG_PATH = Config::get('LOG_PATH', __DIR__ . '/../../logs');
            $config = [
                'logFile' => "{$LOG_PATH}/{$key}.log",
                'logLevel' => \Monolog\Logger::DEBUG
            ];
        }

        $this->pushHandler(new StreamHandler($config['logFile'], $config['logLevel']));
    }

    public static function getInstance($key = "app", $config = null)
    {
        if (empty(self::$loggers[$key])) {
            self::$loggers[$key] = new Logger($key, $config);
        }

        return self::$loggers[$key];
    }

    public static function enableSystemLogs()
    {

        $LOG_PATH = Config::get('LOG_PATH', __DIR__ . '/../../logs');

        // Error Log
        self::$loggers['error'] = new Logger('errors');
        self::$loggers['error']->pushHandler(new StreamHandler("{$LOG_PATH}/errors.log"));
        ErrorHandler::register(self::$loggers['error']);

        // Request Log
        $data = [
            $_SERVER,
            $_REQUEST,
            trim(file_get_contents("php://input"))
        ];
        self::$loggers['request'] = new Logger('request');
        self::$loggers['request']->pushHandler(new StreamHandler("{$LOG_PATH}/request.log"));
        self::$loggers['request']->info("REQUEST", $data);
    }
}

اکنون ما دو تابع اصلی داریم ()Logger::enableSystemLogs این گزارش‌های درخواست و خطاهای ما را فعال خواهد کرد. سپس ما ()Logger::getInstance را داریم که به طور پیش‌فرض گزارش برنامه ما خواهد بود. بیایید آن را تست کنیم. یکبار دیگر index.php را با این خطوط جدید دوباره اصلاح کنیم:

<?php require __DIR__ . '/vendor/autoload.php';

use App\Lib\Config;
$LOG_PATH = Config::get('LOG_PATH', '');
echo "[LOG_PATH]: $LOG_PATH";

//New Lines
use App\Lib\Logger;

Logger::enableSystemLogs();
$logger = Logger::getInstance();
$logger->info('Hello World');

8000:php -S localhost را تایپ کنید. این دستور یک وب سرور داخلی را اجرا می‌کند که از نسخه PHP 5.4 به بعد وجود دارد. به آدرس http://localhost:8000 بروید، باید "LOG_PATH" را ببینید، اما اگر پوشه گزارش‌های خود را بررسی کنید، دو فایل را مشاهده خواهید کرد که یکی محتوای درخواستی را نشان می‌دهد و دیگری حاوی متن "Hello World" است. اگر نیازی به نمایش اطلاعات خاص یا حذف آن‌ها دارید، کمی وقت بگذارید و درخواست را تغییر دهید، این کار به منظور نشان دادن انواع مختلف ورود به سیستم است.

در آخر اجازه دهید فایل index.php خود را کمی تمیز و مرتب کرده و یک فایل جدید به نام App / Lib / App.php ایجاد کنیم، بگذارید از این به عنوان یک بوت استرپ برای برنامه خود استفاده کنیم.

App/Lib/App.php

<?php namespace App\Lib;

class App
{
    public static function run()
    {
        Logger::enableSystemLogs();
    }
}

و بعد index.php را به روز رسانی کنید.

<?php require __DIR__ . '/vendor/autoload.php';
use App\Lib\App;

App::run();

اضافه کردن Routing

در هر برنامه مدرن، مسیریابی بخش بزرگی را شامل می‌شود. با این کار یک کد خاص براساس مسیر در URL انتخابی فراخوانی می‌شود. به عنوان مثال می‌تواند صفحه اصلی را نشان دهد. post/1/ می‌تواند اطلاعات پست را با شناسه 1 نشان دهد، برای این منظور ما سه کلاسRouter.php ، Request.php و Response.php را پیاده‌سازی خواهیم کرد.

کلاس Router.php بسیار ابتدایی خواهد بود، این کلاس متد درخواست را تأیید می‌کند و مسیری را که با استفاده از regex در نظر می‌گیریم تطبیق می‌دهد. اگر مطابقت داشته باشد، تابع برگشتی داده شده توسط ما را با دو پارامتر درخواست و پاسخ اجرا می‌کند.

کلاس Request.php دارای برخی توابع برای دریافت داده‌هایی است که در درخواست ارسال شده‌اند. به عنوان مثال داده‌های Post مانند عنوان و متن ایجاد شده برای آن.

کلاس Response.php دارای برخی توابع برای خروجی به عنوان JSON با وضعیت HTTP خواهد بود.

بنابراین App/Lib/Router.php , App/Lib/Request.php , App/Lib/Response.php را ایجاد کنید.

App/Lib/Router.php

<?php namespace App\Lib;

class Router
{
    public static function get($route, $callback)
    {
        if (strcasecmp($_SERVER['REQUEST_METHOD'], 'GET') !== 0) {
            return;
        }

        self::on($route, $callback);
    }

    public static function post($route, $callback)
    {
        if (strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') !== 0) {
            return;
        }

        self::on($route, $callback);
    }

    public static function on($regex, $cb)
    {
        $params = $_SERVER['REQUEST_URI'];
        $params = (stripos($params, "/") !== 0) ? "/" . $params : $params;
        $regex = str_replace('/', '\/', $regex);
        $is_match = preg_match('/^' . ($regex) . '$/', $params, $matches, PREG_OFFSET_CAPTURE);

        if ($is_match) {
            // first value is normally the route, lets remove it
            array_shift($matches);
            // Get the matches as parameters
            $params = array_map(function ($param) {
                return $param[0];
            }, $matches);
            $cb(new Request($params), new Response());
        }
    }
}

App/Lib/Request.php

<?php namespace App\Lib;

class Request
{
    public $params;
    public $reqMethod;
    public $contentType;

    public function __construct($params = [])
    {
        $this->params = $params;
        $this->reqMethod = trim($_SERVER['REQUEST_METHOD']);
        $this->contentType = !empty($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
    }

    public function getBody()
    {
        if ($this->reqMethod !== 'POST') {
            return '';
        }

        $body = [];
        foreach ($_POST as $key => $value) {
            $body[$key] = filter_input(INPUT_POST, $key, FILTER_SANITIZE_SPECIAL_CHARS);
        }

        return $body;
    }

    public function getJSON()
    {
        if ($this->reqMethod !== 'POST') {
            return [];
        }

        if (strcasecmp($this->contentType, 'application/json') !== 0) {
            return [];
        }

        // Receive the RAW post data.
        $content = trim(file_get_contents("php://input"));
        $decoded = json_decode($content);

        return $decoded;
    }
}

App/Lib/Response.php

<?php

namespace App\Lib;

class Response
{
    private $status = 200;

    public function status(int $code)
    {
        $this->status = $code;
        return $this;
    }
    
    public function toJSON($data = [])
    {
        http_response_code($this->status);
        header('Content-Type: application/json');
        echo json_encode($data);
    }
}

سپس فایل index.php را با کد زیر به روز رسانی کنید.

index.php

<?php
require __DIR__ . '/vendor/autoload.php';

use App\Lib\App;
use App\Lib\Router;
use App\Lib\Request;
use App\Lib\Response;

Router::get('/', function () {
    echo 'Hello World';
});

Router::get('/post/([0-9]*)', function (Request $req, Response $res) {
    $res->toJSON([
        'post' =>  ['id' => $req->params[0]],
        'status' => 'ok'
    ]);
});

App::run();

برای تست آن php -S localhost:8000 را تایپ کنید و به http://localhost:8000 بروید و باید "Hello World" و http://localhost:8000/post/1 را ببینید، همچنین باید یک پاسخ JSON با وضعیت ‘ok’ مشاهده کنید به علاوه id که در "Post" به آن دادید.

{"status": "ok", "post": { "id" : 1} }

اگر از Apache استفاده می‌کنید، ممکن است لازم باشد فایل htaccess. را به روت پروژه خود اضافه کنید.

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php [QSA,L]

و برای Nginx

location / {
  try_files $uri $uri/ /index.php$is_args$args;
}

بسیار عالی، تا به اینجا خوب پیش رفته‌ایم. برنامه ما اکنون مسیریابی دارد. در ادامه بیایید یک کنترلر ساده اضافه کنیم. اگر می‌خواهید از یک موتور الگو مانند Twig استفاده کنید که در آینده مفید خواهد بود.

فایل App/Controller/Home.php را ایجاد کنید.

<?php namespace App\Controller;

class Home
{
    public function indexAction()
    { 
        // you could add the twig package 'composer require "twig/twig:^2.0"' 
        // and use it as "echo $twig->render('index', ['name' => 'Fabien']);"
        echo 'Hello World';
    }
}

و Router::get('/',..) را در فایل index.php اصلاح کنید.

use App\Controller\Home;

Router::get('/', function () {
   (new Home())->indexAction();
});

ادامه مقاله را در بخش دوم دنبال کنید.

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
عرفان حشمتی @heshmati74
مهندس معماری سیستم های کامپیوتری، طراح و توسعه دهنده وب سایت
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو