فهرست سوالات رایج و پرکاربرد در حیطه برنامه‌نویسی PHP
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 17 دقیقه

فهرست سوالات رایج و پرکاربرد در حیطه برنامه‌نویسی PHP

PHP یک زبان برنامه‌نویسی سمت سرور و محبوب است که عمدتا برای توسعه وب سایت‌ها و وب اپلیکیشن‌ها استفاده می‌شود. همچنین می‌توان از آن برای ایجاد وب سایت‌های استاتیک یا داینامیک نیز استفاده کرد. یادگیری آن هم بسیار ساده است. بنابراین در این مقاله قصد داریم 11 سوال متداول در مورد PHP را بررسی کنیم.

1. چگونه می‌توان از SQL Injection در PHP جلوگیری کرد؟

از دستورات آماده و کوئری‌های پارامتری استفاده کنید.

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

شما اساسا دو راه برای دستیابی به این هدف دارید:

اول استفاده از PDO (برای هر درایور پایگاه داده پشتیبانی شده):

$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');

$stmt->execute([ 'name' => $name ]);

foreach ($stmt as $row) {

    // Do something with $row

}

دوم استفاده از MySQLi (برای MySQL):

$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
 
$stmt->execute();
 
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    // Do something with $row
}

اگر هم به پایگاه داده‌ای غیر از MySQL متصل می‌شوید، گزینه دیگری وجود دارد که می‌توانید به آن مراجعه کنید (برای مثال ()pg_prepare و ()pg_execute برای PostgreSQL). PDO یک راهکار همگانی است.

تنظیم صحیح اتصال

توجه داشته باشید که هنگام استفاده از PDO برای دسترسی به پایگاه داده MySQL از دستورات آماده به طور پیش فرض استفاده نمی‌شود. برای رفع این مشکل باید شبیه‌سازی دستورات آماده را غیرفعال کنید. یک مثال از ایجاد اتصال با استفاده از PDO در زیر می‌بینید:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');
 
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

در مثال بالا Error Mode کاملا ضروری نیست، اما توصیه می‌شود آن را اضافه کنید. به این ترتیب هنگامی که مشکلی پیش می‌آید، روند اجرا با Fatal Error متوقف نمی‌شود و به توسعه‌دهندگان این فرصت را می‌دهد هرگونه خطا را که به عنوان PDOExceptions پرتاب می‌شود، به خوبی مدیریت کنند.

اما مورد اجباری، اولین خط ()setAttribute است که به PDO می‌گوید دستورات آماده را غیرفعال کرده و از دستورات واقعی استفاده کند. این امر باعث می‌شود که عبارات و مقادیر توسط PHP قبل از ارسال به سرور MySQL تجزیه نشوند (به مهاجم فرصتی برای تزریق SQL مخرب داده نمی‌شود).

اگرچه می‌توانید charset را در گزینه‌های constructor تنظیم کنید، اما توجه داشته باشید که نسخه‌های قدیمی PHP (قبل از 5.3.6) کاملا بی سر و صدا پارامتر charset را در DSN نادیده می‌گیرند.

دستور SQL که برای prepare ارسال می‌کنید تجزیه و توسط سرور پایگاه داده کامپایل می‌شود. با تعیین پارامترها (یک ? یا یک پارامتر نام گذاری شده مانند name: در مثال بالا) به موتور پایگاه داده می‌گویید که در کجا می‌خواهید آن را فیلتر کنید. سپس وقتی execute را فراخوانی می‌کنید، دستور آماده با مقادیر پارامتری که مشخص کرده‌اید ترکیب می‌شود.

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

هر پارامتری که هنگام استفاده از دستور آماده ارسال می‌کنید، فقط به عنوان رشته تلقی می‌شود (اگرچه موتور پایگاه داده ممکن است بهینه‌سازی‌هایی را انجام دهد، البته پارامترها می‌توانند به اعداد نیز تبدیل شوند). در مثال بالا اگر متغیر name$ حاوی 'Sarah'; DELETE FROM employees باشد، نتیجه فقط جستجوی رشته "'Sarah'; DELETE FROM employees" خواهد بود و در نهایت با یک جدول خالی مواجه نخواهید شد.

یکی دیگر از مزایای استفاده از دستورات آماده این است که اگر یک عبارت را چندین بار در یک session اجرا کنید، فقط یک بار تجزیه و کامپایل می‌شود و به شما سرعت بالایی می‌دهد. البته نحوه انجام این کار برای insert در قالب یک مثال (با استفاده از PDO) آورده شده است:

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
 
$preparedStatement->execute([ 'column' => $unsafeValue ]);

آیا می‌توان از دستورات آماده برای کوئری‌های داینامیک استفاده کرد؟

با اینکه می‌توانید از دستورات آماده برای پارامترهای کوئری استفاده کنید، اما ساختار کوئری داینامیک به خودی خود نمی‌تواند پارامترسازی شود (یعنی برخی از ویژگی‌های آن را نمی‌توان پارامتر کرد).

برای چنین سناریوی خاصی، بهترین کار این است که از فیلتر لیست سفید (whitelist filter) استفاده کنید که مقادیر احتمالی را محدود می‌کند.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

2. چگونه می‌توان بررسی کرد که یک رشته حاوی یک کلمه خاص است؟

بدین منظور می‌توانید از تابع ()strpos که برای یافتن یک رشته در رشته دیگر به کار می‌رود، استفاده کنید:

$a = 'How are you?';
 
if (strpos($a, 'are') !== false) {
    echo 'true';
}

توجه داشته باشید که نوشتن false ==! عمدی است (نه != false و نه === true نتیجه دلخواه را برنمی‌گرداند). در متد ()strpos پارامتر اول رشته مورد نظر و پارامتر دوم کلمه‌ای است که جستجو می‌شود، اگر کلمه پیدا نشد false را برمی‌گرداند. از آنجا که 0 یک آفست معتبر و false است، بنابراین نمی‌توانیم از ساختار ساده‌تری مانند !strpos($a, 'are') استفاده کنیم.

راه حل جایگزین:

همچنین می‌توانید از Regular Expression استفاده نمایید، این روش برای تطبیق کلمات در مقایسه با strpos بهتر است، زیرا برای رشته‌هایی مانند fare، care، stare و ... نیز درست عمل می‌کند. یک تطبیق ساده برای کلمه are می‌تواند چیزی شبیه به این باشد:

$a = 'How are you?';
 
if (preg_match('/\bare\b/', $a)) {
    echo 'true';
}

از نظر عملکرد، strpos تقریبا سه برابر سریعتر است. در نظر داشته باشید وقتی یک میلیون مقایسه را یکباره انجام دادیم، preg_match حدودا 1.5 ثانیه طول کشید و برای strops نزدیک به 0.5 ثانیه به طول انجامید.

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

$a = 'How are you?';
$search = 'are y';
if(preg_match("/{$search}/i", $a)) {
    echo 'true';
}

i در انتهای عبارت، آن را به حروف کوچک تبدیل می‌کند. اگر این مورد را نمی‌خواهید، می توانید آن را کنار بگذارید.

در حال حاضر این می‌تواند در برخی موارد کاملا مشکل ساز باشد، زیرا رشته search$ اصلا sanitize نشده است؛ به این معنی که ممکن است چک نشود، زیرا اگر search$ ورودی کاربر باشد، می‌توان رشته‌ای را اضافه کرد که مانند یک عبارت متفاوت رفتار کند. همچنین در اینجا یک ابزار عالی به نام Regex101 برای تست و مشاهده توضیحات عبارات منظم مختلف آورده شده است.

برای ترکیب هر دو مجموعه در یک تابع چند منظوره (از جمله با حساسیت قابل انتخاب) می‌توانید از چیزی شبیه به این استفاده کنید:

function FindString($needle,$haystack,$i,$word)
{   // $i should be "" or "i" for case insensitive
    if (strtoupper($word)=="W")
    {   // if $word is "W" then word search instead of string in string search.
        if (preg_match("/\b{$needle}\b/{$i}", $haystack)) 
        {
            return true;
        }
    }
    else
    {
        if(preg_match("/{$needle}/{$i}", $haystack)) 
        {
            return true;
        }
    }
    return false;
    // Put quotes around true and false above to return them as strings instead of as bools/ints.
}

3. چرا نباید از توابع mysql_* در PHP استفاده کرد؟

افزونه MySQL محدودیت‌های زیر را دارد:

  • تحت توسعه فعال نیست.
  • از PHP 5.5 به بعد رسما منسوخ شده است.
  • به طور کامل در PHP 7.0 حذف شده است.

این بدان معناست که از تاریخ 31 دسامبر 2018 در هیچ نسخه پشتیبانی شده از PHP وجود ندارد. اگر هم از نسخه‌ای از PHP استفاده می‌کنید که از آن پشتیبانی می‌نماید، در نظر داشته باشید که خطرات احتمالی را به همراه خواهد داشت.

  • فاقد رابط OO است.
  • موارد زیر را پشتیبانی نمی‌کند:
  • کوئری‌های asynchronous  و non-blocking
  • دستورات آماده یا کوئری‌های پارامتری
  • رویه‌های ذخیره شده
  • عبارات چندگانه
  • تراکنش‌ها
  • متد جدید احراز هویت (به طور پیش فرض در MySQL 5.6، مورد نیاز در نسخه 5.7)
  • هر یک از قابلیت‌های جدید در MySQL 5.1 یا بالاتر

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

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

4. چگونه می‌توان عنصر را از یک آرایه در PHP حذف کرد؟

روشهای مختلفی برای حذف یک عنصر آرایه وجود دارد که هر یک از آن‌ها بسته به شرایط می‌توانند مفید واقع شوند.

حذف یک عنصر آرایه

اگر می‌خواهید فقط یک عنصر آرایه را حذف کنید، می‌توانید از متد ()unset یا به عنوان راهی جایگزین از ()array_splice\ استفاده نمایید. همچنین اگر مقدار را دارید ولی اندیس عنصر مورد نظر را نمی‌دانید، می‌توانید از ()array_search\ برای دریافت اندیس بهره بگیرید.

متد unset()

توجه داشته باشید که وقتی از ()unset استفاده می‌کنید، اندیس‌های آرایه تغییر نمی‌یابند. اما اگر می‌خواهید مقادیر را دوباره اندیس‌گذاری کنید، می‌توانید از ​متد ​()array_values\ پس از تعریف unset() کمک بگیرید تا همه اندیس‌ها را به اندیس‌های عددی که از صفر شروع می‌شوند، تبدیل کند.

کد:

<?php
 
    $array = [0 => "a", 1 => "b", 2 => "c"];
    unset($array[1]);
                //↑ Key which you want to delete
 
?>

خروجی:

[
    [0] => a
    [2] => c
]

متد ()array_splice

اگر از ()array_splice\ استفاده می‌کنید، مقادیر به طور خودکار اندیس‌گذاری می‌شوند. اما اندیس‌های وابسته در مقابل ()array_values\ که همه آن‌ها را به اندیس‌های عددی تبدیل می‌کند، تغییر نمی‌یابد. همچنین ()array_splice\ به عنوان پارامتر دوم به آفست نیاز دارد نه اندیس!

کد:

<?php
 
    $array = [0 => "a", 1 => "b", 2 => "c"];
    \array_splice($array, 1, 1);
                        //↑ Offset which you want to delete
 
?>

خروجی:

[
    [0] => a
    [1] => c
]

()array_splice همانند ()unset آرایه را با مرجع می‌گیرد؛ به این معنی که شما نمی‌توانید مقادیر بازگشتی آن توابع را به آرایه اختصاص دهید.

حذف چند عنصر آرایه

اگر قصد دارید چند عنصر آرایه را حذف کنید ولی نمی‌خواهید چندین بار ()unset یا ()array_splice را فراخوانی کنید، بسته به اینکه مقادیر یا اندیس‌های آن را می‌دانید، می‌توانید با استفاده از توابع ()array_diff یا ()array_diff_key عناصری را که می‌خواهید حذف نمایید.

متد ()array_diff

اگر مقادیر عناصری که می‌خواهید حذف کنید را می‌دانید، می‌توانید از () array_diff\ بهره بگیرید. این نیز مانند ()unset اندیس‌های آرایه را تغییر نمی‌دهد.

کد:

<?php

    $array = [0 => "a", 1 => "b", 2 => "c"];
    $array = \array_diff($array, ["a", "c"]);
                               //└────────┘→ Array values which you want to delete
?>

خروجی:

[
    [1] => b
]

متد ()array_diff_key

اگر اندیس‌های عناصری که می‌خواهید حذف کنید را می‌دانید، می‌توانید از ()array_diff_key\ استفاده کنید. در اینجا باید مطمئن شوید که اندیس‌ها را به عنوان کلیدهای پارامتر دوم ارسال می‌کنید و نه به عنوان مقادیر. در غیر این صورت باید آرایه را توسط متد ()array_flip\ برعکس کنید. همچنین در اینجا نیز اندیس‌ها تغییر نمی‌کنند.

کد:

<?php
 
    $array = [0 => "a", 1 => "b", 2 => "c"];
    $array = \array_diff_key($array, [0 => "xy", "2" => "xy"]);
                                    //↑           ↑ Array keys which you want to delete
?>

خروجی:

[
    [1] => b
]

به علاوه اگر می‌خواهید از ()unset یا ()array_splice\ برای حذف چندین عنصر با مقدار یکسان استفاده کنید، می‌توانید از متد ()array_keys\ به منظور به دست آوردن همه اندیس‌های یک مقدار خاص و سپس حذف همه عناصر کمک بگیرید.

5. آیا راهی برای دریافت Thumbnail از API یوتیوب وجود دارد؟

هر ویدیوی یوتیوب دارای چهار تصویر تولید شده است و قالب‌بندی آن‌ها نیز به صورت زیر است:

https://img.youtube.com/vi/<insert-youtube-video-id-here>/0.jpg
https://img.youtube.com/vi/<insert-youtube-video-id-here>/1.jpg
https://img.youtube.com/vi/<insert-youtube-video-id-here>/2.jpg
https://img.youtube.com/vi/<insert-youtube-video-id-here>/3.jpg

اولین مورد در لیست یک تصویر در اندازه کامل است و سایر موارد thumbnail هستند. تصویر thumbnail پیش فرض (یعنی یکی از 1.jpg، 2.jpg و 3.jpg) به این شکل است:

https://img.youtube.com/vi/<insert-youtube-video-id-here>/default.jpg

برای نسخه با کیفیت بالا از URL مشابه زیر استفاده می‌کنیم:

https://img.youtube.com/vi/<insert-youtube-video-id-here>/hqdefault.jpg

همچنین برای نسخه متوسط URL مشابه HQ است:

https://img.youtube.com/vi/<insert-youtube-video-id-here>/mqdefault.jpg

برای نسخه با کیفیت استاندارد نیز از URL مشابه زیر استفاده کنید:

https://img.youtube.com/vi/<insert-youtube-video-id-here>/mqdefault.jpg

برای حداکثر رزولوشن تصویر هم از این URL استفاده نمایید:

https://img.youtube.com/vi/<insert-youtube-video-id-here>/maxresdefault.jpg

همه URLهای فوق از طریق HTTP نیز در دسترس هستند. به علاوه نام هاست کوتاه‌تری به عنوان i3.ytimg.com در URLهای مثال بالا به جای img.youtube.com قرار می‌گیرد.

به عنوان روش دوم می‌توانید از YouTube Data API (v3) برای دریافت تصاویر thumbnail استفاده کنید.

راه حل جایگزین:

به منظور بازیابی تصاویر thumbnail، عنوان، توضیحات، امتیاز، آمار و موارد دیگر می‌توانید از YouTube Data API کمک بگیرید. API نسخه 3 به یک key* نیاز دارد. کلید را به دست آورید و یک درخواست video: list ایجاد کنید:

https://www.googleapis.com/youtube/v3/videos?key=YOUR_API_KEY&part=snippet&id=VIDEO_ID

کد:

$data = file_get_contents("https://www.googleapis.com/youtube/v3/videos?key=YOUR_API_KEY&part=snippet&id=T0Jqdjbed40");
$json = json_decode($data);
var_dump($json->items[0]->snippet->thumbnails);

خروجی:

object(stdClass)#5 (5) {
  ["default"]=>
  object(stdClass)#6 (3) {
    ["url"]=>
    string(46) "https://i.ytimg.com/vi/T0Jqdjbed40/default.jpg"
    ["width"]=>
    int(120)
    ["height"]=>
    int(90)
  }
  ["medium"]=>
  object(stdClass)#7 (3) {
    ["url"]=>
    string(48) "https://i.ytimg.com/vi/T0Jqdjbed40/mqdefault.jpg"
    ["width"]=>
    int(320)
    ["height"]=>
    int(180)
  }
  ["high"]=>
  object(stdClass)#8 (3) {
    ["url"]=>
    string(48) "https://i.ytimg.com/vi/T0Jqdjbed40/hqdefault.jpg"
    ["width"]=>
    int(480)
    ["height"]=>
    int(360)
  }
  ["standard"]=>
  object(stdClass)#9 (3) {
    ["url"]=>
    string(48) "https://i.ytimg.com/vi/T0Jqdjbed40/sddefault.jpg"
    ["width"]=>
    int(640)
    ["height"]=>
    int(480)
  }
  ["maxres"]=>
  object(stdClass)#10 (3) {
    ["url"]=>
    string(52) "https://i.ytimg.com/vi/T0Jqdjbed40/maxresdefault.jpg"
    ["width"]=>
    int(1280)
    ["height"]=>
    int(720)
  }
}

نه تنها به یک کلید نیاز دارید، بلکه ممکن است بسته به تعداد درخواست‌های API که قصد ایجاد آنها را دارید، اطلاعات صورت حساب نیز از شما خواسته شود. با این وجود، چند میلیون درخواست در روز کاملا رایگان است.

6. چه زمانی از self بعد از this$ استفاده کنیم؟

برای اشاره به شی در حال استفاده از this$ و برای ارجاع به کلاس در حال استفاده از self استفاده می‌کنیم. به عبارت دیگر، از $this->member برای عضوهای غیراستاتیک و از self::$member برای عضوهای ثابت می‌توان استفاده کرد.

در اینجا نمونه‌ای از طرز استفاده صحیح this$ و self برای متغیرهای غیراستاتیک و استاتیک آورده شده است:

<?php
class X {
    private $non_static_member = 1;
    private static $static_member = 2;
 
    function __construct() {
        echo $this->non_static_member . ' '
           . self::$static_member;
    }
}
 
new X();
?>

در زیر هم نمونه‌ای از استفاده نادرست this$ و self برای انواع متغیرهای ذکر شده قرار دادیم:

<?php
class X {
    private $non_static_member = 1;
    private static $static_member = 2;
 
    function __construct() {
        echo self::$non_static_member . ' '
           . $this->static_member;
    }
}
 
new X();
?>

همچنین نمونه‌ای از پلی‌مورفیسم با استفاده از this$ برای توابع در اینجا می‌بینید:

<?php
class X {
    function foo() {
        echo 'X::foo()';
    }
 
    function bar() {
        $this->foo();
    }
}
 
class Y extends X {
    function foo() {
        echo 'Y::foo()';
    }
}
 
$x = new Y();
$x->bar();
?>

مثالی از نحوه رفتار پلی‌مورفیسم با استفاده از self برای توابع در زیر آمده است:

<?php
class X {
    function foo() {
        echo 'X::foo()';
    }
 
    function bar() {
        self::foo();
    }
}
 
class Y extends X {
    function foo() {
        echo 'Y::foo()';
    }
}
 
$x = new Y();
$x->bar();
?>

به این صورت که عبارت $this->foo() تابع ()foo را از شی فعلی فراخوانی می‌کند. اگر شی از نوع X باشد، بدین ترتیب ()X::foo فراخوانی شده اما اگر شی از نوع Y باشد، ()Y::foo فراخوانی می‌شود. با این تفاوت که درself::foo() ، همیشه ()X::foo فراخوانی می‌گردد.

راه حل جایگزین:

کلمه کلیدی self صرفا به کلاس فعلی اشاره نمی‌کند، حداقل نه به صورتی که شما را محدود به عضوهای استاتیک کند. در یک عضو غیراستاتیک، self راهی برای دور زدن vtable در شی فعلی ارائه می‌دهد. همانطور که می‌توانید از parent::methodName() برای فراخوانی نسخه والد یک تابع استفاده نمایید، بدین صورت هم می‌توانید self::methodName() را برای پیاده‌سازی کلاس‌های جاری یک متد فراخوانی کنید.

class Person {
    private $name;
 
    public function __construct($name) {
        $this->name = $name;
    }
 
    public function getName() {
        return $this->name;
    }
 
    public function getTitle() {
        return $this->getName()." the person";
    }
 
    public function sayHello() {
        echo "Hello, I'm ".$this->getTitle()."<br/>";
    }
 
    public function sayGoodbye() {
        echo "Goodbye from ".self::getTitle()."<br/>";
    }
}
 
class Geek extends Person {
    public function __construct($name) {
        parent::__construct($name);
    }
 
    public function getTitle() {
        return $this->getName()." the geek";
    }
}
 
$geekObj = new Geek("Ludwig");
$geekObj->sayHello();
$geekObj->sayGoodbye();

خروجی به این شکل خواهد بود:

Hello, I’m Ludwig the geek Goodbye from Ludwig the person

()sayHello از اشاره‌گر this$ استفاده میکند، بنابراین vtable برای ()Geek::getTitle فراخوانی میشود. ()sayGoodbye هم از ()self::getTitle استفاده می‌کند، بنابراین vtable به کار گرفته نشده و ()Person::getTitle فراخوانی می‌گردد. در هر دو مورد، ما با متد شی نمونه‌سازی شده سر و کار داشته و در توابع فراخوانی شده به اشاره‌گر this$ دسترسی داریم.

7. چگونه می‌توان خطاهای PHP را برای نمایش در صفحه دریافت کرد؟

برای این کار می‌توانید به صورت زیر عمل کنید:

ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);

هر چند این باعث نمی‌شود که PHP خطاهای parse را نشان دهد. تنها راه نشان دادن چنین خطاهایی این است که فایل php.ini خود را با خط زیر تغییر دهید:

display_errors = on

اگر به php.ini دسترسی ندارید، قرار دادن خط زیر در htaccess. نیز ممکن است کار کند:

php_flag display_errors 1

8. چگونه می‌توان دو تابع نوشت که رشته‌ای را بگیرند و در صورتی که با کاراکتر یا رشته مشخص‌شده شروع شود و یا پایان یابد، آن را برگرداند؟

بدین منظور می‌توانید مطابق زیر عمل کنید:

function startsWith($haystack, $needle)
{
     $length = strlen($needle);
     return (substr($haystack, 0, $length) === $needle);
}
 
function endsWith($haystack, $needle)
{
    $length = strlen($needle);
    if ($length == 0) {
        return true;
    }
 
    return (substr($haystack, -$length) === $needle);
}

اگر هم نمی‌خواهید از regex استفاده کنید، به صورت زیر عمل نمایید.

راه حل جایگزین:

برای بررسی شروع و پایان می‌توانید از تابع substr_compare کمک بگیرید:

function startsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}
function endsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

9. چگونه می‌توان در PHP عمل redirect انجام داد؟

راه حل اصولی:

می‌توانید از متد ()header برای ارسال هدر HTTP استفاده کنید، اما این باید قبل از HTML یا هر متن دیگری (به عنوان مثال قبل از تعریف <!DOCTYPE ...>) به مرورگر ارسال شود.

header('Location: '.$newURL);

راه حل جایگزین:

استفاده از die() یا exit():

header("Location: http://example.com/myOtherPage.php");
die();

URL مطلق یا نسبی

از ژوئن 2014 به بعد می‌توان از URLهای مطلق و نسبی استفاده کرد. RFC 7231 را بررسی کنید که جایگزین RFC 2616 قدیمی شده بود، جایی که فقط URLهای مطلق مجاز بودند.

کدهای وضعیت

هدر Location هنوز از کد وضعیت HTTP 302 بهره می‌گیرد، اما این چیزی نیست که شما حتما باید از آن استفاده کنید. شما باید 301 (تغییر مسیر دائمی) یا 303 را در نظر بگیرید.

توجه: W3C بیان می‌کند که هدر 303 با بسیاری از ایجنت‌های کاربر قبل از HTTP/1.1 سازگار نیست. مرورگرهای مورد استفاده در حال حاضر همگی ایجنت‌های کاربر HTTP/1.1 را دارند. این موضوع همچنین در مورد بسیاری از موارد دیگر مانند spiderها و robotها نیز صادق نیست.

مستندات

هدرهای HTTP و متد ()header در PHP

جایگزین‌ها

همچنین می‌توانید از روش جایگزین http_redirect($url); استفاده کنید که برای نصب به پکیج PECL نیاز دارد.

توابع کمکی

این تابع کد 303 را شامل نمی‌شود:

function Redirect($url, $permanent = false)
{
    header('Location: ' . $url, true, $permanent ? 301 : 302);
 
    exit();
}
 
Redirect('http://example.com/', false);

شکل انعطاف پذیرتر:

function redirect($url, $statusCode = 303)
{
   header('Location: ' . $url, true, $statusCode);
   die();
}

روش فرعی

همانطور که گفته شد، تغییر مسیر در header() فقط قبل از اینکه چیزی نوشته شود کار می‌کند و در صورت استفاده در خروجی HTML معمولا جواب نمی‌دهد. بنابراین می‌توان از یک راه حل هدر HTML (روش نه خیلی حرفه‌ای) مانند زیر کمک گرفت:

<meta http-equiv="refresh" content="0;url=finalpage.html">

یا حتی redirect جاوااسکریپت:

window.location.replace("http://example.com/");

راه حل دیگر:

از تابع ()header برای ارسال هدر HTTP Location استفاده کنید:

header('Location: '.$newURL);

برخلاف تصور برخی افراد، die() هیچ ارتباطی با redirect ندارد. فقط در صورتی که می‌خواهید به جای اجرای معمولی redirect کنید، از آن کمک بگیرید.

 فایل example.php:

<?php
    header('Location: static.html');
    $fh = fopen('/tmp/track.txt', 'a');
    fwrite($fh, $_SERVER['REMOTE_ADDR'] . ' ' . date('c') . "\n");
    fclose($fh);
?>

نتیجه سه اجرا:

[email protected]:~> cat /tmp/track.txt
127.0.0.1 2009-04-21T09:50:02+02:00
127.0.0.1 2009-04-21T09:50:05+02:00
127.0.0.1 2009-04-21T09:50:08+02:00

ارسال هدر صرف نظر از کلاینت مورد استفاده، اجرای PHP را متوقف نمی‌کند.

10. چگونه می‌توان از bcrypt برای رمزنگاری پسورد در PHP استفاده کرد؟

bcrypt یک الگوریتم هش (hash) است که با هر سخت‌افزاری مقیاس‌پذیری دارد (از طریق تعداد دور قابل تنظیم). چند دوره بودن آن اطمینان می‌دهد که مهاجم باید هزینه و سخت‌افزار عظیمی را در اختیار داشته باشد تا بتواند رمزهای عبور شما را بشکند. آن را روی پسوردهای خود اعمال کنید و مطمئن باشید که حمله عملا غیرممکن خواهد شد.

bcrypt از الگوریتم Eksblowfish برای هش رمز عبور استفاده می‌کند. با اینکه مرحله رمزنگاری Eksblowfish و Blowfish دقیقا یکسان هستند، مرحله برنامه‌ریزی کلیدی Eksblowfish اطمینان می‌دهد که هر حالت بعدی به salt و key (رمز عبور کاربر) وابسته است و هیچ وضعیتی را نمی‌توان بدون اطلاع هر دو مورد از پیش محاسبه کرد. به دلیل این تفاوت کلیدی، bcrypt یک الگوریتم هش یک طرفه است. یعنی شما نمی‌توانید رمز عبور را بدون اطلاع از salt، تعداد دورهای رمزنگاری شده و key (رمز عبور) بازیابی کنید.

نحوه استفاده از bcrypt:

توابع هش رمز عبور مستقیما در PHP> = 5.5 ساخته شده‌اند. اکنون می‌توانید از ()password_hash برای ایجاد یک هش bcrypt برای هر پسوردی استفاده کنید:

<?php
// Usage 1:
echo password_hash('rasmuslerdorf', PASSWORD_DEFAULT)."\n";
// $2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// For example:
// $2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a
 
// Usage 2:
$options = [
  'cost' => 11
];
echo password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options)."\n";
// $2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C

به منظور تأیید رمز عبور ارائه شده توسط کاربر در برابر هش موجود، می‌توانید از ()password_verify به صورت زیر کمک بگیرید:

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';
 
if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}

بهره‌گیری ازPHP>= 5.3.7  (همچنین RedHat PHP>= 5.3.3)

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

بهره‌گیری از PHP <5.3.7 (DEPRECATED)

همچنین می‌توانید از تابع ()crypt برای ایجاد هش‌های bcrypt رشته‌های ورودی استفاده کنید. این کلاس می‌تواند به طور خودکار salt تولید کرده و هش‌های موجود را در برابر ورودی تأیید کند. اگر PHP 5.3.7 یا بالاتر دارید، توصیه می‌شود از تابع داخلی یا کتابخانه سازگار استفاده نمایید. این جایگزین فقط برای بهره‌گیری از نسخه‌های قدیمی ارائه شده است.

class Bcrypt{
  private $rounds;
 
  public function __construct($rounds = 12) {
    if (CRYPT_BLOWFISH != 1) {
      throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt");
    }
 
    $this->rounds = $rounds;
  }
 
  public function hash($input){
    $hash = crypt($input, $this->getSalt());
 
    if (strlen($hash) > 13)
      return $hash;
 
    return false;
  }
 
  public function verify($input, $existingHash){
    $hash = crypt($input, $existingHash);
 
    return $hash === $existingHash;
  }
 
  private function getSalt(){
    $salt = sprintf('$2a$%02d$', $this->rounds);
 
    $bytes = $this->getRandomBytes(16);
 
    $salt .= $this->encodeBytes($bytes);
 
    return $salt;
  }
 
  private $randomState;
  private function getRandomBytes($count){
    $bytes = '';
 
    if (function_exists('openssl_random_pseudo_bytes') &&
        (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL is slow on Windows
      $bytes = openssl_random_pseudo_bytes($count);
    }
 
    if ($bytes === '' && is_readable('/dev/urandom') &&
       ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) {
      $bytes = fread($hRand, $count);
      fclose($hRand);
    }
 
    if (strlen($bytes) < $count) {
      $bytes = '';
 
      if ($this->randomState === null) {
        $this->randomState = microtime();
        if (function_exists('getmypid')) {
          $this->randomState .= getmypid();
        }
      }
 
      for ($i = 0; $i < $count; $i += 16) {
        $this->randomState = md5(microtime() . $this->randomState);
 
        if (PHP_VERSION >= '5') {
          $bytes .= md5($this->randomState, true);
        } else {
          $bytes .= pack('H*', md5($this->randomState));
        }
      }
 
      $bytes = substr($bytes, 0, $count);
    }
 
    return $bytes;
  }
 
  private function encodeBytes($input){
    // The following is code from the PHP Password Hashing Framework
    $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
 
    $output = '';
    $i = 0;
    do {
      $c1 = ord($input[$i++]);
      $output .= $itoa64[$c1 >> 2];
      $c1 = ($c1 & 0x03) << 4;
      if ($i >= 16) {
        $output .= $itoa64[$c1];
        break;
      }
 
      $c2 = ord($input[$i++]);
      $c1 |= $c2 >> 4;
      $output .= $itoa64[$c1];
      $c1 = ($c2 & 0x0f) << 2;
 
      $c2 = ord($input[$i++]);
      $c1 |= $c2 >> 6;
      $output .= $itoa64[$c1];
      $output .= $itoa64[$c2 & 0x3f];
    } while (true);
 
    return $output;
  }
}

می‌توانید از کد بالا به این شکل هم استفاده کنید:

$bcrypt = new Bcrypt(15);
 
$hash = $bcrypt->hash('password');
$isGood = $bcrypt->verify('password', $hash);

همچنین می‌توانید از Portable PHP Hashing Framework هم کمک بگیرید.

11. چگونه می‌توان از PHP برای به دست آوردن تاریخ جاری استفاده کرد؟

برای این کار می‌توانید از date یا strftime استفاده کنید.

 به عنوان مثال:

<?php echo date("Y"); ?>

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

برای قالب‌بندی تاریخ‌ها به زبان‌های دیگر باید از توابع ()setlocale و ()strftime به جای ()date استفاده کرد.

بدین منظور اگر از راه دور مجبور به کنترل برنامه خود هستید، بهتر است تا حد امکان از strftime کمک بگیرید. اگر در این مورد مشکلی ندارید، یکی را به دلخواه انتخاب کنید.

سخن پایانی

اینها 11 سوال متداول در مورد PHP بودند. در صورت داشتن هرگونه نظر یا ابهام می‌توانید در بخش زیر به ما اطلاع دهید.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
5 از 1 رای

6 ماه پیش
php
/@heshmati74
عرفان حشمتی
Full-Stack Web Developer

مهندس معماری سیستم های کامپیوتری، طراح و توسعه دهنده وب سایت

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید

ورود یا ثبت‌نام

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

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

عرفان حشمتی

Full-Stack Web Developer