وسوم 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
اقرأ أيضًا
- المقال التالي: معالجة الصور مع مكتبة GD ومكتبة Imagick في PHP
- المقال السابق: التعامل مع واجهة سطر الأوامر (CLI) في PHP
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.