اذهب إلى المحتوى

وسوم PHP

يجب أن تستخدم دائمًا الوسوم ‎<?php ?>‎ أو وسوم الطباعة القصيرة ‎<?= ?>‎، ويجب ألا تُستخدم الاختلافات الأخرى (خاصةً الوسوم القصيرة <? ?>) لأنّ مديري النظام يعطلونها عادةً.

يجب تجاهل صيغة الإغلاق ‎?>‎ عندما لا نتوقع أن ينتج الملف خرجًا لتجنب الخرج غير المقصود الذي يمكن أن يسبب مشاكل عندما يحلل العميل الملف خاصةً أنّ بعض المتصفحات تفشل في التعرف على وسم ‎<!DOCTYPE وتنشّط نمط التجاوزات Quirks Mode.

مثال عن سكربت PHP بسيط:

<?php
print "Hello World";

مثال عن ملف تعريف صنف:

<?php
class Foo
{
    ...
}

مثال عن PHP مضمن في HTML:

<ul id="nav">
    <?php foreach ($navItems as $navItem): ?>
        <li><a href="<?= htmlspecialchars($navItem->url) ?>">
            <?= htmlspecialchars($navItem->label) ?>
        </a></li>
    <?php endforeach; ?>
</ul>

فوائد المولّدات (Generators)

تقدّم PHP 5.5 المولّدات والكلمة المفتاحية yield التي تسمح لنا بكتابة شيفرة غير متزامنة تبدو أشبه بالشيفرة المتزامنة، يعدّ التعبير yield مسؤولًا عن إعادة التحكم إلى الشيفرة المستدعاة وتوفير نقطة استئناف من هناك، يمكنك إرسال قيمة مع تعليمة yield، القيمة المرجعة من هذا التعبير إما null أو القيمة الممررة إلى Generator::send()‎.

function reverse_range($i) {
    // ‫مجرد وجود الكلمة المفتاحية `yield` في هذه الدالة يجعلها مولّد
    do {
        // ‎$i‎ هي القيمة المُحتفظ بها بين الاستئنافات
        print yield $i;
    } while (--$i > 0);
}

$gen = reverse_range(5);
print $gen->current();

// الإرسال أيضًا يستأنف المولِّد
$gen->send("injected!");

foreach ($gen as $val) {
    // المرور على كامل محتويات المولّد مما يجعله يستأنف عند كل تكرار
    echo $val;
}

// 5injected!4321

يمكن استخدام هذه الآلية بتنفيذ نمط مشترك (coroutine) لانتظار كائنات Awaitable المُعادة من المولِّد (بتسجيل المولّد نفسه كرد نداء للحل) ومواصلة تنفيذ المولّد بمجرد إنهاء كائن Awaitable.

استخدام حلقة حدث من مكتبة Icicle

تستخدم مكتبة Icicle كائنات Awaitable ومولّدات لإنشاء نمط مشترك.

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

use Icicle\Awaitable;
use Icicle\Coroutine\Coroutine;
use Icicle\Loop;

$generator = function (float $time) {
    try {
        // ‫ضبط المتغير ‎$start إلى القيمة المعادة من الدالة microtime()‎? بعد ‎$time ثانية تقريبًا
        $start = yield Awaitable\resolve(microtime(true))->delay($time);
        echo "Sleep time: ", microtime(true) - $start, "\n";

        // ‫رمي استثناء من كائن Awaitable المرفوض إلى النمط المشترك
        return yield Awaitable\reject(new Exception('Rejected awaitable'));
    } catch (Throwable $e) {
        // ‫التقاط سبب رفض awaitable
        echo "Caught exception: ", $e->getMessage(), "\n";
    }
    return yield Awaitable\resolve('Coroutine completed');
};

// يبقى النمط المشترك ساكنًا لمدة 1.2 ثانية ثم ينتهي معيدًا سلسلة نصية
$coroutine = new Coroutine($generator(1.2));
$coroutine->done(function (string $data) {
    echo $data, "\n";
});

Loop\run();

إنتاج عمليات غير معطَّلة مع proc_open()‎

لا تدعم PHP تنفيذ الشيفرة بشكلٍ متزامن إلا إذا ثبَّت الإضافات مثل pthread، يمكن تجاوز هذا أحيانًا باستخدام الدوال proc_open()‎ وstream_set_blocking()‎­ وقراءة خرجهم بشكلٍ غير متزامن.

يمكننا تنفيذ الشيفرة كعمليات فرعية متعددة إذا قسمناها إلى أجزاء أصغر، ثمّ يمكننا جعل كل عملية فرعية غير معطَّلة باستخدام دالة stream_set_blocking()‎ أي أنّه يمكننا إنتاج عدة عمليات فرعية ثم التحقق من خرجها في حلقة (بشكل مشابه لحلقة حدث) والانتظار حتى تنتهي جميعها.

يمكن أن يكون لدينا مثلًا عملية فرعية صغيرة تنفّذ حلقة وتتوقف في كل تكرار بشكلٍ عشوائي لمدة 100- 1000 ميلي ثانية (لاحظ أنّ التأخير هو نفسه لكل عملية فرعية).

<?php
// subprocess.php
$name = $argv[1];
$delay = rand(1, 10) * 100;
printf("$name delay: ${delay}ms\n");

for ($i = 0; $i < 5; $i++) {
    usleep($delay * 1000);
    printf("$name: $i\n");
}

ثم ستنتج العملية الرئيسية عمليات فرعية وتقرأ خرجها، ويمكننا تقسيمه إلى كتل أصغر:

  • إنتاج عمليات فرعية باستخدام proc_open()‎.
  • جعل كل عملية فرعية غير معطَّلة باستخدام stream_set_blocking()‎.
  • تنفيذ حلقة حتى تنتهي كل العمليات الفرعية باستخدام proc_get_status()‎.
  • إغلاق مقابض الملف بشكل صحيح مع أنبوب الخرج لكل عملية فرعية باستخدام fclose()‎ وإغلاق مقابض العملية باستخدام proc_close()‎.
<?php
// non-blocking-proc_open.php
// واصفات الملف لكل عملية فرعية
$descriptors = [
    ᠎᠎0 => ['pipe', 'r'], // stdin
    1 => ['pipe', 'w'], // stdout
];

$pipes = [];
$processes = [];
foreach (range(1, 3) as $i) {
    // إنتاج عملية فرعية
    $proc = proc_open('php subprocess.php proc' . $i, $descriptors, $procPipes);
    $processes[$i] = $proc;
    // جعل العملية الفرعية غير معطَّلة (أنبوب الخرج فقط)
    stream_set_blocking($procPipes[1], 0);
    $pipes[$i] = $procPipes;
}

// تنفيذ حلقة حتى تنتهي كل العمليات الفرعية
while (array_filter($processes, function($proc) { return proc_get_status($proc)['running']; })) {
    foreach (range(1, 3) as $i) {
        usleep(10 * 1000); // 100ms
        // ‫قراءة كل الخرج الممكن (الخرج غير المقروء يُخزَّن مؤقتًا)
        $str = fread($pipes[$i][1], 1024);
        if ($str) {
            printf($str);
        }
    }
}

// إغلاق كل الأنابيب والعمليات
foreach (range(1, 3) as $i) {
    fclose($pipes[$i][1]);
    proc_close($processes[$i]);
}

يحتوي الخرج على مزيج من العمليات الفرعية الثلاث بما أننا نقرأها باستخدام fread()‎، لاحظ أنّه في المثال انتهت العملية proc1 قبل العمليتين الباقيتين بكثير.

$ php non-blocking-proc_open.php
proc1 delay: 200ms
proc2 delay: 1000ms
proc3 delay: 800ms
proc1: 0
proc1: 1
proc1: 2
proc1: 3
proc3: 0
proc1: 4
proc2: 0
proc3: 1
proc2: 1
proc3: 2
proc2: 2
proc3: 3
proc2: 3
proc3: 4
proc2: 4

قراءة منفذ تسلسلي مع إضافة حدث ودخل/خرج مباشر

إنّ مجاري الدخل والخرج المباشرة (DIO) غير معروفة الآن من قِبل الإضافة حدث Event، فلا توجد طريقة نظيفة للحصول على واصف الملف مغلفًا ضمن موارد الدخل والخرج المباشرة، إلا أنّ هناك حل بديل:

  • فتح مجرى للمنفذ باستخدام fopen()‎.
  • جعل المجرى غير معطّل باستخدام stream_set_blocking();‎.
  • الحصول على واصف ملف رقمي من المجرى باستخدام EventUtil::getSocketFd();‎.
  • تمرير واصف الملف الرقمي إلى الدالة dio_fdopen()‎ والحصول على مورد دخل/خرج مباشر.
  • إضافة حدث مع رد نداء للتنصت على الأحداث المقروءة على واصف الملف.
  • تُصرَف البيانات المتاحة في رد النداء وتُعالج وفقًا لمنطق تطبيقك.

الملف dio.php:

<?php
class Scanner {
    // ‫مسار المنفذ مثل  ‎/dev/pts/5
    protected $port;
    // واصف الملف الرقمي
    protected $fd;
    // EventBase
    protected $base;
    // مورد دخل/خرج مباشر 
    protected $dio;
    // حدث
    protected $e_open; 
    // حدث
    protected $e_read;

    public function __construct ($port) {
        $this->port = $port;
        $this->base = new EventBase();
    }

    public function __destruct() {
        $this->base->exit();
        if ($this->e_open)
            $this->e_open->free();
        if ($this->e_read)
            $this->e_read->free();
        if ($this->dio)
            dio_close($this->dio);
    }

    public function run() {
        $stream = fopen($this->port, 'rb');
        stream_set_blocking($stream, false);
        $this->fd = EventUtil::getSocketFd($stream);
        if ($this->fd < 0) {
            fprintf(STDERR, "Failed attach to port, events: %d\n", $events);
            return;
        }
        $this->e_open = new Event($this->base, $this->fd, Event::WRITE, [$this, '_onOpen']);
        $this->e_open->add();
        $this->base->dispatch();
        fclose($stream);
    }

    public function _onOpen($fd, $events) {
        $this->e_open->del();
        $this->dio = dio_fdopen($this->fd);

        // استدعاء دوال دخل/خرج مباشر هنا
        dio_tcsetattr($this->dio, [
            'baud' => 9600,
            'bits' => 8,
            'stop' => 1,
            'parity' => 0
        ]);

        $this->e_read = new Event($this->base, $this->fd, Event::READ | Event::PERSIST,
        [$this, '_onRead']);
        $this->e_read->add();
    }

    public function _onRead($fd, $events) {
        while ($data = dio_read($this->dio, 1)) {
            var_dump($data);
        }
        }
    }

// تغيير وسيط المنفذ
$scanner = new Scanner('/dev/pts/5');
$scanner->run();

الاختبار: نفّذ التعليمة التالية في الطرفية A:

$ socat -d -d pty,raw,echo=0 pty,raw,echo=0
2016/12/01 18:04:06 socat[16750] N PTY is /dev/pts/5
2016/12/01 18:04:06 socat[16750] N PTY is /dev/pts/8
2016/12/01 18:04:06 socat[16750] N starting data transfer loop with FDs [5,5] and [7,7]

قد يختلف الخرج، استخدم الطرفيات الزائفة من أول سطرين (‎/dev/pts/5‎ و‎/dev/pts/8 بالتحديد).

نفّذ في الطرفية B السكربت السابق، قد تحتاج إلى صلاحيات الجذر:

$ sudo php dio.php

أرسل من الطرفية C سلسلة نصية إلى الطرفية الزائفة الأولى:

$ echo test > /dev/pts/8

الخرج:

string(1) "t"
string(1) "e"
string(1) "s"
string(1) "t"
string(1) "
"

عميل HTTP بالاعتماد على الإضافة Event

إليك مثال عن صنف عميل HTTP بالاعتماد على الإضافة Event، يسمح هذا الصنف بجدولة عدد من طلبات HTTP ثم تنفيذها بشكلٍ غير متزامن.

ملف http-client.php:

<?php
class MyHttpClient {
    // ‫متغير من الصنف EventBase
    protected $base;
    // مصفوفة كائنات من الصنف EventHttpConnection
    protected $connections = [];

    public function __construct() {
        $this->base = new EventBase();
    }

    // ‫دالة لإرسال كل الطلبات المعلقة (أحداث)، تُرجع void
    public function run() {
        $this->base->dispatch();
    }

    public function __destruct() {
        // ‫تدمير كائنات الاتصال بشكلٍ صريح، لا تنتظر كانس المهملات (GC) وإلا قد يتحرر كائن EventBase باكرًا
        $this->connections = null;
    }

    // (1)
    public function addRequest($address, $port, array $headers,
$cmd = EventHttpRequest::CMD_GET, $resource = '/'){
        $conn = new EventHttpConnection($this->base, null, $address, $port);
        $conn->setTimeout(5);
        $req = new EventHttpRequest([$this, '_requestHandler'], $this->base);
        foreach ($headers as $k => $v) {
            $req->addHeader($k, $v, EventHttpRequest::OUTPUT_HEADER);
        }
        $req->addHeader('Host', $address, EventHttpRequest::OUTPUT_HEADER);
        $req->addHeader('Connection', 'close', EventHttpRequest::OUTPUT_HEADER);
        if ($conn->makeRequest($req, $cmd, $resource)) {
            $this->connections []= $conn;
            return $req;
        }
        return false;
    }

    // (2)
    public function _requestHandler($req, $unused) {
        if (is_null($req)) {
            echo "Timed out\n";
        } else {
            $response_code = $req->getResponseCode();
            if ($response_code == 0) {
                echo "Connection refused\n";
            } elseif ($response_code != 200) {
                echo "Unexpected response: $response_code\n";
            } else {
                echo "Success: $response_code\n";
                $buf = $req->getInputBuffer();
                echo "Body:\n";
                while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
                    echo $s, PHP_EOL;
                }
            }
        }
    }
}

$address = "my-host.local";
$port = 80;
$headers = [ 'User-Agent' => 'My-User-Agent/1.0', ];

$client = new MyHttpClient();

// إضافة طلبات معلقة
for ($i = 0; $i < 10; $i++) {
    $client->addRequest($address, $port, $headers,
EventHttpRequest::CMD_GET, '/test.php?a=' . $i);
}

// إرسال طلبات معلقة
$client->run();

في الموضع (1) نضيف طلب HTTP معلق، معاملاته هي:

  • ‎$address: اسم المضيف أو IP، سلسلة نصية.
  • ‎$port: رقم المنفذ، عدد صحيح.
  • ‎$headers: ترويسات HTTP إضافية، مصفوفة.
  • ‎$cmd: ثابت EventHttpRequest::CMD_*‎، عدد صحيح.
  • ‎$resource: مورد طلب HTTP مثل '‎/page?a=b&c=d'، سلسلة نصية. القيمة المعادة إما EventHttpRequest أو false.

في الموضع (2) نضيف دالة لمعالجة طلب HTTP، معاملاتها:

  • ‎$req، كائن من الصنف EventHttpRequest.
  • ‎$unused، خليط من المعاملات.

تعيد هذه الدالة void.

الملف test.php، مثال عن سكربت من جهة الخادم:

<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;
echo 'User-Agent: ', $_SERVER['HTTP_USER_AGENT'] ?? '(none)', PHP_EOL;

الاستخدام:

php http-client.php

مثال عن الخرج:

Success: 200
Body:
GET: array (
    'a' => '1',
)
User-Agent: My-User-Agent/1.0
Success: 200
Body:
GET: array (
    'a' => '0',
)
User-Agent: My-User-Agent/1.0
Success: 200
Body:
GET: array (
    'a' => '3',
)
...
// الخرج مختصر

لاحظ أنّ الشيفرة صُممت للمعالجة طويلة الأمد في CLI SAPI.

عميل HTTP بالاعتماد على الإضافة Ev

إليك مثال عن صنف عميل HTTP بالاعتماد على الإضافة Ev.

تنفذ الإضافة Ev حدث حلقة بسيط لكن قوي للأغراض العامة، إنّها لا توفر مراقبين خاصين للشبكة لكن يمكن استخدام I/O watcher الخاص بالإضافة للمعالجة غير المتزامنة للمقابس.

تظهر الشيفرة التالية كيف يمكن جدولة طلبات HTTP للمعالجة التفرعية.

ملف http-client.php:

<?php
    class MyHttpRequest {
    // ‫كائن من الصنف MyHttpClient
    private $http_client;
    // سلسلة نصية
    private $address;
    // ‫مورد HTTP من النوع سلسلة نصية مثل ‎/page?get=param
    private $resource;
    // ‫طريقة HTTP من النوع سلسلة نصية مثل GET، ‏POST 
    private $method;
    // عدد صحيح
    private $service_port;
    // مقبس مورد
    private $socket;
    // مهلة الاتصال بالثانية من النوع عدد عشري 
    private $timeout = 10.;
    // ‫حجم كل جزء للدالة socket_recv()‎ بالبايتات من النوع عدد صحيح
    private $chunk_size = 20;
    ?// ‫كائن من الصنف EvTimer
    private $timeout_watcher;
    // ‫كائن من الصنف EvIo
    private $write_watcher;
    // ‫كائن من الصنف EvIo
    private $read_watcher;
    // ‫كائن من الصنف EvTimer
    private $conn_watcher;
    // مخزن مؤقت للبيانات القادمة من النوع سلسلة نصية
    private $buffer;
    // الأخطاء التي أخبرت عنها إضافة المقابس في وضع عدم التعطيل من النوع مصفوفة
    private static $e_nonblocking = [
        // ‫عُطِّلت العملية لكن وُضع واصف الملف في وضع عدم التعطيل (EAGAIN أو EWOULDBLOCK)
        11, 
        // ‫العملية الحالية قيد التقدم (EINPROGRESS)
        115,
    ];

    // (1)
    public function __construct(MyHttpClient $client, $host, $resource, $method) {
        $this->http_client = $client;
        $this->host = $host;
        $this->resource = $resource;
        $this->method = $method;

        // ‫الحصول على المنفذ من خدمة WWW
        $this->service_port = getservbyname('www', 'tcp');

        // ‫الحصول على عنوان IP للمضيف الهدف
        $this->address = gethostbyname($this->host);

        // ‫إنشاء مقبس TCP/IP
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if (!$this->socket) {
            throw new RuntimeException("socket_create() failed: reason: " .
socket_strerror(socket_last_error()));
        }

        // ‫ضبط الراية O_NONBLOCK
        socket_set_nonblock($this->socket);
        $this->conn_watcher = $this->http_client->getLoop()
        ->timer(0, 0., [$this, 'connect']);
    }

    public function __destruct() {
        $this->close();
    }

    private function freeWatcher(&$w) {
        if ($w) {
            $w->stop();
            $w = null;
        }
    }

    // تحرير كل موارد الطلب
    private function close() {
        if ($this->socket) {
            socket_close($this->socket);
            $this->socket = null;
        }
        $this->freeWatcher($this->timeout_watcher);
        $this->freeWatcher($this->read_watcher);
        $this->freeWatcher($this->write_watcher);
        $this->freeWatcher($this->conn_watcher);
    }

    // دالة تهيئ اتصالًا بالمقبس وتعيد قيمة منطقية
    public function connect() {
        $loop = $this->http_client->getLoop();
        $this->timeout_watcher = $loop->timer($this->timeout, 0., [$this, '_onTimeout']);
        $this->write_watcher = $loop->io($this->socket, Ev::WRITE, [$this, '_onWritable']);
        return socket_connect($this->socket, $this->address, $this->service_port);
    }

    // ‫رد نداء لمهلة المراقب (EvTimer)
    public function _onTimeout(EvTimer $w) {
        $w->stop();
        $this->close();
    }

    // رد نداء يُستدعى عندما يصبح المقبس قابلًا للكتابة
    public function _onWritable(EvIo $w) {
        $this->timeout_watcher->stop();
        $w->stop();
        $in = implode("\r\n", [
            "{$this->method} {$this->resource} HTTP/1.1",
            "Host: {$this->host}",
'Connection: Close',
]) . "\r\n\r\n";
        if (!socket_write($this->socket, $in, strlen($in))) {
            trigger_error("Failed writing $in to socket", E_USER_ERROR);
            return;
        }
        $loop = $this->http_client->getLoop();
        $this->read_watcher = $loop->io($this->socket,
Ev::READ, [$this, '_onReadable']);
        // الاستمرار بتنفيذ الحلقة
        $loop->run();
    }

    // رد نداء يُستدعى عندما يصبح المقبس قابلًا للقراءة
    public function _onReadable(EvIo $w) {
        // استقبال 20 بايت في وضع عدم التعطيل
        $ret = socket_recv($this->socket, $out, 20, MSG_DONTWAIT);

        if ($ret) {
            // إذا كان لا يزال هناك بيانات للقراءة، أضفها إلى المخزن المؤقت
            $this->buffer .= $out;
        } elseif ($ret === 0) {
            // إذا قُرِأت كل البيانات
            printf("\n<<<<\n%s\n>>>>", rtrim($this->buffer));
            fflush(STDOUT);
            $w->stop();
            $this->close();
            return;
        }

        // ‫التقاط EINPROGRESS، ‏EAGAIN أو EWOULDBLOCK
        if (in_array(socket_last_error(), static::$e_nonblocking)) {
            return;
        }
        $w->stop();
        $this->close();
    }
}

/////////////////////////////////////
class MyHttpClient {
    // ‫مصفوفة كائنات من الصنف MyHttpRequest
    private $requests = [];
    ?// ‫متغير من الصنف EvLoop
    private $loop;

    public function __construct() {
        // ‫ينفذ كل عميل HTTP حلقة حدث خاصة به
        $this->loop = new EvLoop();
    }

    public function __destruct() {
        $this->loop->stop();
    }

    // ‫تعيد هذه الدالة كائن EvLoop
    public function getLoop() {
        return $this->loop;
    }

    // إضافة طلبات معلقة
    public function addRequest(MyHttpRequest $r) {
        $this->requests []= $r;
    }

    // إرسال كل الطلبات المعلقة
    public function run() {
        $this->loop->run();
    }
}

//// الاستخدام
$client = new MyHttpClient();
foreach (range(1, 10) as $i) {
    $client->addRequest(new MyHttpRequest($client, 'my-host.local', '/test.php?a=' . $i, 'GET'));
}

$client->run();

في الموضع (1) معاملات الدالة هي:

  • ‎$client من الصنف MyHttpClient.
  • ‎$host اسم المضيف مثل google.co.uk، سلسلة نصية
  • ‎$resource مورد HTTP مثل ‎/page?a=b&c=d، سلسلة نصية.
  • ‎$method طريقة HTTP مثل: GET، ‏HEAD، ‏POST، ‏PUT…، سلسلة نصية. ترمي هذه الدالة الاستثناء RuntimeException.

الاختبار: بفرض أنّ سكربت http://my-host.local/test.php يطبع محتويات ‎$_GET:

<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;

سيكون عندها خرج الأمر php http-client.php مشابهًا للتالي:

<<<<
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Fri, 02 Dec 2016 12:39:54 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/7.0.13-pl0-gentoo

1d
GET: array (
    'a' => '3',
)

0
>>>>
<<<<
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Fri, 02 Dec 2016 12:39:54 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/7.0.13-pl0-gentoo

1d
GET: array (
    'a' => '2',
)
0
>>>>
...

// الخرج مختصر

لاحظ أنّ إضافة المقابس في PHP 5 قد تسجل تحذيرات لقيم الخطأ EINPROGRESS وEAGAIN وEWOULDBLOCK، من الممكن تعطيل هذه التسجيلات بكتابة الشيفرة:

error_reporting(E_ERROR);

استخدام حلقة الحدث Amp

تستفاد مكتبة العمل Amp من الوعود (اسم آخر لكائنات Awaitables) والمولِّدات لإنشاء نمط مشترك.

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

use Amp\Dns;

// جرب الأسرع محللنا المعرّف من قِبل النظام أو غوغل
function queryStackOverflow($recordtype) {
    $requests = [
        Dns\query("stackoverflow.com", $recordtype),
        Dns\query("stackoverflow.com", $recordtype, ["server" => "8.8.8.8"]),
    ];

    // تعيد وعدًا ينتهي عندما ينتهي أول طلب
    return yield Amp\first($request);
}
\Amp\run(function() {
    // الحلقة الأساسية، نمط مشترك ضمنيًا
    try {
        // التحويل إلى نمط مشترك باستخدام Amp\resolve()‎
        $promise = Amp\resolve(queryStackOverflow(Dns\Record::NS));
        list($ns, $type, $ttl) =
        // ‫نحتاج إلى نتيجة NS واحدة وليس كل النتائج
        current(yield Amp\timeout($promise, 2000 /* milliseconds */));
        echo "The result of the fastest server to reply to our query was $ns";
    } catch (Amp\TimeoutException $e) {
        echo "We've heard no answer for 2 seconds! Bye!";
    } catch (Dns\NoRecordException $e) {
        echo "No NS records there? Stupid DNS nameserver!";
    }
});

التوطين (Localization)

توطين السلاسل النصية مع gettext()‎

gettext من مكتبة GNU هي إضافة PHP يجب تضمصينها ضمن ملف php.ini:

extension=php_gettext.dll #Windows
extension=gettext.so #Linux

تنفذ دوال gettext واجهة برمجة تطبيقات دعم اللغة الأصلية (NLS) والتي يمكن استخدامها لتوطين تطبيقات PHP.

يمكن إجراء السلاسل النصية للترجمة في PHP بضبط المحلية (locale) وضبط جداول الترجمة واستدعاء gettext()‎ على أي سلسلة نصية تريد ترجمتها.

<?php
// ضبط اللغة إلى الفرنسية
putenv('LC_ALL= fr_FR');
setlocale(LC_ALL, 'fr_FR');

// ‫تحديد موقع جداول الترجمة للنطاق 'myPHPApp'
bindtextdomain("myPHPApp", "./locale");

// ‫اختيار النطاق 'myPHPApp'
textdomain("myPHPApp");

الملف myPHPApp.po:

#: /Hello_world.php:56
msgid "Hello"
msgstr "Bonjour"

#: /Hello_world.php:242
msgid "How are you?"
msgstr "Comment allez-vous?"

تحمّل الدالة gettext()‎ ملف ‎.po بعد تصريفه أي ملف ‎.mo، الذي يربط ملفك ليصبح سلاسل نصية مترجمة كما في الأعلى.

بعد هذه الشيفرة البسيطة سيُنظر إلى الترجمة في الملف التالي:

./locale/fr_FR/LC_MESSAGES/myPHPApp.mo

عندما تستدعي gettext('some string')‎، إذا كانت السلسلة النصية 'some string' مُترجمة في الملف ‎.mo ستُرجع الترجمة وإلا ستُرجع السلسلة 'some string' غير مترجمة.

// ‫طباعة النسخة المترجمة من 'Welcome to My PHP Application'
echo gettext("Welcome to My PHP Application");

// ‫أو نستخدم الاسم البديل `‎_()‎` للدالة `gettext()‎`
echo _("Have a nice day");

معالجة الترويسات

الضبط الأساسي للترويسة

إليك الضبط الأساسي لترويسة للانتقال إلى صفحة جديدة عند الضغط على زر:

if(isset($_REQUEST['action']))
{
    switch($_REQUEST['action'])
    { // ضبط الترويسة بالاعتماد على أي الزر المضغوط
        case 'getState':
            header("Location: http://NewPageForState.com/getState.php?search=" . $_POST['search']);
            break;
        case 'getProject':
            header("Location: http://NewPageForProject.com/getProject.php?search=" . $_POST['search']);
        break;
    }
else
{
    GetSearchTerm(!NULL);
}
// نماذج لإضافة ولاية أو مشروع والضغط على البحث
function GetSearchTerm($success)
{
    if (is_null($success))
    {
        echo "<h4>You must enter a state or project number</h4>";
    }
    echo "<center><strong>Enter the State to search for</strong></center><p></p>";
// ‫استخدام `‎$_SERVER['PHP_SELF']‎` يبقينا في الصفحة حتى تقرر تعليمة `switch` أين سنذهب
    echo "<form action='" . $_SERVER['PHP_SELF'] . "' enctype='multipart/form-data' method='POST'>
<input type='hidden' name='action' value='getState'>
<center>State: <input type='text' name='search' size='10'></center><p></p>
<center><input type='submit' name='submit' value='Search State'></center>
</form>";
    GetSearchTermProject($success);
}
function GetSearchTermProject($success)
{
    echo "<center><br><strong>Enter the Project to search for</strong></center><p></p>";
    echo "<form action='" . $_SERVER['PHP_SELF'] . "' enctype='multipart/form-data' method='POST'>
<input type='hidden' name='action' value='getProject'>
<center>Project Number: <input type='text' name='search' size='10'></center><p></p>
<center><input type='submit' name='submit' value='Search Project'></center>
</form>";
}
?>

كيفية كشف عنوان IP لعميل

الاستخدام المناسب للترويسة HTTPXFORWARDED_FOR

يوجد متغير آخر يُستخدم بشكلٍ سيء على نطاق واسع في ضوء أحدث ثغرات httpoxy، تُستخدم الترويسة HTTP_X_FORWARDED_FOR غالبًا لكشف عنوان IP لعميل، لكن قد يؤدي ذلك بدون أي عمليات تحقق إضافية إلى مشاكل في الأمان خاصةً عند استخدام عنوان IP هذا لاحقًا للمصادقة أو في استعلامات SQL بدون تعقيم.

تتجاهل معظم أمثلة الشيفرة المتوفرة حقيقة أنّه يمكن أن نعد HTTP_X_FORWARDED_FOR معلومةً يوفرها العميل بنفسه ولذا فهي مصدر غير موثوق لاكتشاف عنوان IP العميل، تضيف بعض هذه الأمثلة تحذيرًا بشأن سوء الاستخدام المحتمل لكنها لا تزال تفتقد إلى القيام بالتحقق في شيفرتها، لذا نقدم لك مثالًا عن دالة مكتوبة في PHP عن كيفية كشف عنوان IP لعميل إذا كنت تعرف أنّ العميل يستخدم وكيلًا (proxy) وأنت تعرف أنّه يمكن الوثوق بهذا الوكيل، إذا لم تكن تعرف أي وكيل موثوق فيمكنك استخدام REMOTE_ADDR فقط.

function get_client_ip()
{
    // لا يوجد شيء لفعله بدون معلومات موثوقة
    if (!isset($_SERVER['REMOTE_ADDR'])) {
        return NULL;
    }

    // ‫الترويسة التي يستخدمها الوكيل الموثوق للإشارة إلى عنوان IP الأصلي
    $proxy_header = "HTTP_X_FORWARDED_FOR";

    // (1)
    $trusted_proxies = array("2001:db8::1", "192.168.50.1");

    if (in_array($_SERVER['REMOTE_ADDR'], $trusted_proxies)) {
        // ‫الحصول على عنوان IP للعميل الذي يستخدم وكيل موثوق
        if (array_key_exists($proxy_header, $_SERVER)) {
            // (2)
            $client_ip = trim(end(explode(",", $_SERVER[$proxy_header])));

            // التحقق فقط في حالة
            if (filter_var($client_ip, FILTER_VALIDATE_IP)) {
                return $client_ip;
            } else {
                // (3)
            }
        }
    }

    // ‫في كل الحالات الباقية REMOTE_ADDR هو عنوان IP الوحيد الذي يمكن الوثوق به
    return $_SERVER['REMOTE_ADDR'];
}
print get_client_ip();

في الموضع (1) نضيف قائمة بكل الوكلاء المعروفين لمعالجة 'proxy_header' بطريقةٍ آمنةٍ.

في الموضع (2) يمكن أن تحتوي الترويسة على عدة عناوين IP لوكلاء تمر عبرها، يمكن الوثوق فقط بعنوان IP الذي أضافه الوكيل الأخير (الموجود في القائمة).

في الموضع (3) فشل التحقق مما يعني فوز الشخص الذي ضَبط الوكيل أو أنشأ قائمة الوكلاء الموثوقين لذا يجب إضافة معالجة للأخطاء هنا والتنبيه على خطأ الشخص المسؤول.

ترجمة -وبتصرف- للفصول [Coding Conventions - Asynchronous programming - Localization - Headers Manipulation - How to Detect Client IP Address] من كتاب PHP Notes for Professionals book

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...