الفرق بين المراجعتين لصفحة: «Twig/advanced»

من موسوعة حسوب
إدخال 2.0 تمام المحتوى وانظر أيضًا والتصانيف والمصادر.
طلا ملخص تعديل
 
(8 مراجعات متوسطة بواسطة مستخدمين اثنين آخرين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:توسيع Twig}}</noinclude>
<noinclude>{{DISPLAYTITLE:توسيع عمل محرك Twig}}</noinclude>
تو{{DISPLAYTITLE:توسيع Twig}}سيع Twig يتم بعدة طرق، إذ تستطيع إضافة وسوم (tags) إضافية وفلاتر واختبارات وعوامل (operators) ومتغيرات عامة (global variables) ودوال أيضًا، بل تستطيع توسيع المحلل (parser) نفسه بزوار العقد (node visitors). لاحظ أن أول جزء من هذه الصفحة يشرح كيفية توسيع Twig، فإذا أردت إعادة استخدام ما تفعله في مشاريع مختلفة أو كنت تريد مشاركتها مع غيرك فيجب أن تنشئ توسيعًا بنفسك كما هو موضح أدناه.
توسيع Twig يتم بعدة طرائق، إذ تستطيع إضافة وسوم tags إضافية ومرشحات واختبارات وعوامل operators ومتغيرات عامة global variables ودوال أيضًا، بل تستطيع توسيع المحلل parser نفسه بزوار العقد node visitors. لاحظ أن أول جزء من هذه الصفحة يشرح كيفية توسيع عمل محرك Twig، فإذا أردت إعادة استخدام ما تفعله في مشاريع مختلفة أو كنت تريد مشاركتها مع غيرك فيجب أن تنشئ توسيعًا بنفسك كما هو موضح أدناه.  


وا{{DISPLAYTITLE:توسيع Twig}}نتبه إلى أن توسيع Twig من غير إنشاء توسيع جديد يجعل Twig غير قادر على إعادة تصريف (recompile) قوالبك عند تحديث شيفرة PHP، ويجب أن تعطل تخزين القوالب المؤقت (template caching) أو تحزِّم شيفرتك في توسيع كي ترى تغييراتك في الوقت الحقيقي، كما سيلي بيانه لاحقًا في هذه الصفحة. كذلك يجب أن تفهم أولًا الاختلافات بين نقاط التوسيع المختلفة ومتى يجب استخدام كل منها، قبل توسيع Twig نفسه، فاعلم أن Twig به بنيتين لغويتين أساسيتين:
وانتبه إلى أن توسيع Twig من غير إنشاء توسعة جديدة extension تجعل Twig غير قادر على إعادة تصريف recompile قوالبك عند تحديث شيفرة [[PHP]]، ويجب أن تعطل تخزين القوالب المؤقت template caching أو تحزِّم شيفرتك في توسيعة كي ترى تغييراتك في الوقت الحقيقي، كما سيلي بيانه لاحقًا في هذه الصفحة.  


* <code><nowiki>{{</nowiki></code>{{DISPLAYTITLE:توسيع Twig}} <code><nowiki>}}</nowiki></code>: تُستخدم لطباعة نتيجة تقييم التعبير.
__toc__
* <code>{%</code>{{DISPLAYTITLE:توسيع Twig}} <code>%}</code>: تُستخدم لتنفيذ التعليمات.


انظر كيفية تنفيذ مولد lorem ipsum -الذي يحتاج أن يعرف عدد الكلمات التي يجب توليدها- كي تفهم كيف يكشف Twig كثيرًا من نقاط التوسيع، استخدم وسم <code>lipsum</code>:<syntaxhighlight lang="twig">
يجب كذلك أن تفهم أولًا الاختلافات بين نقاط التوسعة extension points المختلفة ومتى يجب استخدام كل منها، قبل توسيع Twig نفسه، فاعلم أن Twig به بنيتين لغويتين أساسيتين:
 
* <code><nowiki>{{ }}</nowiki></code>: تُستخدم لطباعة نتيجة تقييم التعبير.
* <code>{% %}</code>: تُستخدم لتنفيذ التعليمات.
 
انظر كيفية تنفيذ مولد النص التلقائي lorem ipsum -الذي يحتاج أن يعرف عدد الكلمات التي يجب توليدها- كي تفهم كيف يكشف Twig كثيرًا من نقاط التوسعة، استخدم وسم <code>lipsum</code>:<syntaxhighlight lang="twig">
{% lipsum 40 %}
{% lipsum 40 %}


سطر 18: سطر 22:
{{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
{{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}


</syntaxhighlight>ولا تحتاج إلى إنشاء وسوم إلا نادرًا، وهذا أمر حسن لأنها أعقد نقاط التوسيع. لنستخدم الآن فلتر <code>lipsum</code>: <syntaxhighlight lang="twig">
</syntaxhighlight>ولا تحتاج إلى إنشاء وسوم إلا نادرًا، وهذا أمر حسن لأنها أعقد نقاط التوسعة. لنستخدم الآن مرشح <code>lipsum</code>: <syntaxhighlight lang="twig">
{{ 40|lipsum }}
{{ 40|lipsum }}


</syntaxhighlight>وهذا يعمل أيضًا لكن يجب أن يحول الفلترُ القيمةَ الممرَّرة إلى شيء آخر، وهنا نستخدم القيمة للإشارة إلى عدد الكلمات التي يجب توليدها، لذا فإن <code>40</code> هي وسيط (argument) للفلتر، وليست القيمة التي نريد تحويلها. لنستخدم الآن دالة <code>lipsum</code>:<syntaxhighlight lang="twig">
</syntaxhighlight>وهذا يعمل أيضًا لكن يجب أن يحول المرشحُ القيمةَ الممرَّرة إلى شيء آخر، وهنا نستخدم القيمة للإشارة إلى عدد الكلمات التي يجب توليدها، لذا فإن <code>40</code> هي وسيط argument للمرشح، وليست القيمة التي نريد تحويلها. لنستخدم الآن دالة <code>lipsum</code>:<syntaxhighlight lang="twig">
{{ lipsum(40) }}
{{ lipsum(40) }}


</syntaxhighlight>ولهذا المثال خاصة فإن نقطة التوسيع التي ستُستخدم هي إنشاء الدالة، وتستطيع استخدامها في أي مكان يقبل تعبيرًا:<syntaxhighlight lang="twig">
</syntaxhighlight>ولهذا المثال خاصة فإن نقطة التوسعة التي ستُستخدم هي إنشاء الدالة، وتستطيع استخدامها في أي مكان يقبل تعبيرًا:<syntaxhighlight lang="twig">
{{ 'some text' ~ lipsum(40) ~ 'some more text' }}
{{ 'some text' ~ lipsum(40) ~ 'some more text' }}


{% set lipsum = lipsum(40) %}
{% set lipsum = lipsum(40) %}
</syntaxhighlight>وأخيرًا تستطيع استخدام كائنًا عامًا (global object) مع تابع قادر على إنشاء نص لوريم إبسوم:<syntaxhighlight lang="twig">
</syntaxhighlight>وأخيرًا تستطيع استخدام كائنًا عامًا global object مع تابع قادر على إنشاء نص لوريم إبسوم:<syntaxhighlight lang="twig">
{{ text.lipsum(40) }}
{{ text.lipsum(40) }}


سطر 39: سطر 43:
!متى
!متى
|-
|-
|شيفرة جامعة (macro)
|شيفرة جامعة macro
|بسيط
|بسيط
|متكرر
|متكرر
|إنشاء المحتوى
|إنشاء المحتوى
|-
|-
|عام (global)
|عام global
|بسيط
|بسيط
|متكرر
|متكرر
|كائن مساعد
|كائن مساعد
|-
|-
|دالة (function)
|دالة function
|بسيط
|بسيط
|متكرر
|متكرر
|تَحوُّل القيمة
|تَحوُّل القيمة
|-
|-
|وسم (tag)
|وسم tag
|معقد
|معقد
|نادر
|نادر
|بنية خاصة لللغة (Domain Specific Language)
|بنية خاصة للغة Domain Specific Language
|-
|-
|اختبار (test)
|اختبار test
|بسيط
|بسيط
|نادر
|نادر
|إجراء بولياني
|إجراء بولياني
|-
|-
|عامل (operator)
|عامل operator
|بسيط
|بسيط
|نادر
|نادر
سطر 70: سطر 74:
|}
|}


== المتغيرات العامة (Global Variables) ==
== المتغيرات العامة Global Variables ==
المتغير العام يشبه أي متغير قالب آخر إلا أنه متاح في جميع القوالب والشيفرات الجامعة:<syntaxhighlight lang="twig">
المتغير العام يشبه أي متغير قالب آخر إلا أنه متاح في جميع القوالب والشيفرات الجامعة:<syntaxhighlight lang="php">
$twig = new \Twig\Environment($loader);
$twig = new \Twig\Environment($loader);
$twig->addGlobal('text', new Text());  
$twig->addGlobal('text', new Text());  
سطر 79: سطر 83:
</syntaxhighlight>
</syntaxhighlight>


== الفلاتر ==
== المرشحات ==
إنشاء الفلتر يكون بربط اسم مع نوع البيانات "callable" من PHP:<syntaxhighlight lang="twig">
إنشاء المرشح يكون بربط اسم مع نوع البيانات "[[PHP/callable|callable]]" من [[PHP]]:<syntaxhighlight lang="php">
// دالة مجهولة
// دالة مجهولة
$filter = new \Twig\TwigFilter('rot13', function ($string) {
$filter = new \Twig\TwigFilter('rot13', function ($string) {
سطر 89: سطر 93:
$filter = new \Twig\TwigFilter('rot13', 'str_rot13');
$filter = new \Twig\TwigFilter('rot13', 'str_rot13');


// (class static method) أو تابع صنف ساكن
// class static method أو تابع صنف ساكن
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
$filter = new \Twig\TwigFilter('rot13', 'SomeClass::rot13Filter');
$filter = new \Twig\TwigFilter('rot13', 'SomeClass::rot13Filter');
سطر 97: سطر 101:
// هذا الأخير يحتاج إلى تطبيق في وقت التشغيل، انظر أدناه للتوضيح.
// هذا الأخير يحتاج إلى تطبيق في وقت التشغيل، انظر أدناه للتوضيح.
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
</syntaxhighlight>يكون أول وسيط ممرَّر إلى منشئ ‎<code>\Twig\TwigFilter</code> هو اسم الفلتر الذي ستستخدمه في القوالب والثاني هو نوع البيانات "callable" المرتبط به. ثم بعد ذلك، نضيف الفلتر إلى بيئة Twig: <syntaxhighlight lang="twig">
</syntaxhighlight>يكون أول وسيط ممرَّر إلى منشئ ‎<code>\Twig\TwigFilter</code> هو اسم المرشح الذي ستستخدمه في القوالب والثاني هو نوع البيانات "[[PHP/callable|callable]]" المرتبط به. ثم بعد ذلك، نضيف المرشح إلى بيئة Twig: <syntaxhighlight lang="php">
$twig = new \Twig\Environment($loader);
$twig = new \Twig\Environment($loader);
$twig->addFilter($filter);
$twig->addFilter($filter);
سطر 104: سطر 108:


{# Gjvt سيُخرج #}
{# Gjvt سيُخرج #}
</syntaxhighlight>يستقبل نوع البيانات "callable" الجانب الأيسر من الفلتر قبل الأنبوب <code>|</code> كوسيط أول عند استدعاء Twig له، وتمرَّر وسائط إضافية إلى الفلتر داخل أقواس <code>()</code>. فمثلًا تُصرَّف الشيفرة التالية:<syntaxhighlight lang="twig">
</syntaxhighlight>يستقبل نوع البيانات "[[PHP/callable|callable]]" (ردود النداء) الجانب الأيسر من المرشح قبل الأنبوب <code>|</code> كوسيط أول عند استدعاء Twig له، وتمرَّر وسائط إضافية إلى المرشح داخل أقواس <code>()</code>. فمثلًا تُصرَّف الشيفرة التالية:<syntaxhighlight lang="twig">
{{ 'TWIG'|lower }}
{{ 'TWIG'|lower }}
{{ now|date('d/m/Y') }}
{{ now|date('d/m/Y') }}
</syntaxhighlight>لتكون كما يلي بعد التصريف:<syntaxhighlight lang="twig">
</syntaxhighlight>لتكون كما يلي بعد التصريف:<syntaxhighlight lang="php">
<?php echo strtolower('TWIG') ?>
<?php echo strtolower('TWIG') ?>
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>
</syntaxhighlight>يأخذ الصنف ‎<code>\Twig\TwigFilter</code> مصفوفة من الخيارات كآخر وسيط له:<syntaxhighlight lang="twig">
</syntaxhighlight>يأخذ الصنف ‎<code>\Twig\TwigFilter</code> مصفوفة من الخيارات كآخر وسيط له:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('rot13', 'str_rot13', $options);
$filter = new \Twig\TwigFilter('rot13', 'str_rot13', $options);


</syntaxhighlight>
</syntaxhighlight>


=== الفلاتر الواعية للبيئة ===
=== المرشحات الواعية للبيئة ===
إذا أردت الوصول إلى نسخة البيئة الحالية في فلترك فاجعل الخيار <code>needs_environment</code> على <code>true</code>، وسيمرر Twig البيئة الحالية كأول وسيط إلى استدعاء الفلتر:<syntaxhighlight lang="twig">
إذا أردت الوصول إلى نسخة البيئة الحالية في مرشحك فاجعل الخيار <code>needs_environment</code> على <code>true</code>، وسيمرر Twig البيئة الحالية كأول وسيط إلى استدعاء المرشح:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $string) {
$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $string) {
     // اجلب مجموعة المحارف الحالية للنسخة.
     // اجلب مجموعة المحارف الحالية للنسخة.
سطر 125: سطر 129:
</syntaxhighlight>
</syntaxhighlight>


=== الفلاتر الواعية للسياق ===
=== المرشحات الواعية للسياق ===
إذا أردت الوصول إلى السياق الحالي في فلترك فاجعل الخيار <code>needs_context</code> على <code>true</code>، وسيمرر Twig السياق الحالي كأول وسيط إلى استدعاء الفلتر أو الوسيط الثاني إذا كان <code>needs_environment</code> على <code>true</code> أيضًا:<syntaxhighlight lang="twig">
إذا أردت الوصول إلى السياق الحالي في مرشحك فاجعل الخيار <code>needs_context</code> على <code>true</code>، وسيمرر Twig السياق الحالي كأول وسيط إلى استدعاء المرشح أو الوسيط الثاني إذا كان <code>needs_environment</code> على <code>true</code> أيضًا:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('rot13', function ($context, $string) {
$filter = new \Twig\TwigFilter('rot13', function ($context, $string) {
     // ...
     // ...
سطر 136: سطر 140:
</syntaxhighlight>
</syntaxhighlight>


=== التهريب التلقائي (Automatic Escaping) ===
=== التهريب التلقائي Automatic Escaping ===
إذا كان التهريب التلقائي مفعلًا فقد يُهرَّب خرج الفلتر قبل الطباعة، وإن كان فلترك يتصرف كمهرِّب أو يخرج شيفرة HTML أو جافاسكربت فربما تريد طباعة الخرج الخام، فاضبط خيار <code>is_safe</code> في مثل تلك الحالة:<syntaxhighlight lang="twig">
إذا كان التهريب التلقائي مفعلًا فقد يُهرَّب خرج المرشح قبل الطباعة، وإن كان مرشحك يتصرف كمهرِّب أو يخرج شيفرة HTML أو جافاسكربت فربما تريد طباعة الخرج الخام، فاضبط خيار <code>is_safe</code> في مثل تلك الحالة:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);
$filter = new \Twig\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);
</syntaxhighlight>قد تحتاج بعض الفلاتر إلى التعامل مع مدخلات مهرَّبة سلفًا أو آمنة، كما في حالة إضافة وسوم HTML آمنة إلى خرج غير آمن، ففي مثل تلك الحالة، اضبط خيار <code>pre_escape</code> ليهرب بيانات الدخل قبل تشغيلها عبر الفلتر الخاص بك:<syntaxhighlight lang="twig">
</syntaxhighlight>قد تحتاج بعض المرشحات إلى التعامل مع مدخلات مهرَّبة سلفًا أو آمنة، كما في حالة إضافة وسوم HTML آمنة إلى خرج غير آمن، ففي مثل تلك الحالة، اضبط خيار <code>pre_escape</code> ليهرب بيانات الدخل قبل تشغيلها عبر المرشح الخاص بك:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);
$filter = new \Twig\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);
</syntaxhighlight>
</syntaxhighlight>


=== الفلاتر المتغيرة (Variadic Filters) ===
=== المرشحات المتغيرة Variadic Filters ===
إذا وجب على فلتر أن يقبل عددًا عشوائيًا من الوسائط، فاجعل خيار <code>is_variadic</code> على <code>true</code>، كي يمرِّر Twig الوسائط الإضافية كوسيط أخير في هيئة مصفوفة إلى استدعاء الفلتر:<syntaxhighlight lang="twig">
إذا وجب على مرشح أن يقبل عددًا عشوائيًا من الوسائط، فاجعل خيار <code>is_variadic</code> على <code>true</code>، كي يمرِّر Twig الوسائط الإضافية كوسيط أخير في هيئة مصفوفة إلى استدعاء المرشح:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('thumbnail', function ($file, array $options = []) {
$filter = new \Twig\TwigFilter('thumbnail', function ($file, array $options = []) {
     // ...
     // ...
}, ['is_variadic' => true]);
}, ['is_variadic' => true]);
</syntaxhighlight>لكن انتبه إلى أن [[Twig/templates|الوسائط المسمَّاة (named arguments)]] التي تمرَّر إلى فلتر متغير لا يمكن التحقق من صلاحيتها بما أنها ستؤول تلقائيًا إلى مصفوفة الخيارات (option array).
</syntaxhighlight>لكن انتبه إلى أن [[Twig/templates|الوسائط المسمَّاة named arguments]] التي تمرَّر إلى مرشح متغير لا يمكن التحقق من صلاحيتها بما أنها ستؤول تلقائيًا إلى مصفوفة الخيارات option array.


=== الفلاتر الديناميكية ===
=== المرشحات الديناميكية ===
الفلتر الديناميكي هو فلتر يحتوي اسمه على المحرف الخاص <code>*</code>، وهذا المحرف مطابق لأي سلسلة نصية:<syntaxhighlight lang="twig">
المرشح الديناميكي هو مرشح يحتوي اسمه على المحرف الخاص <code>*</code>، وهذا المحرف مطابق لأي سلسلة نصية:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('*_path', function ($name, $arguments) {
$filter = new \Twig\TwigFilter('*_path', function ($name, $arguments) {
     // ...
     // ...
});
});
</syntaxhighlight>الفلاتر التالية مطابقة للفلتر الديناميكي المعرَّف أعلاه:
</syntaxhighlight>المرشحات التالية مطابقة للمرشح الديناميكي المعرَّف أعلاه:


* <code>product_path</code>
* <code>product_path</code>
* <code>category_path</code>
* <code>category_path</code>


يستطيع الفلتر الديناميكي أن يعرِّف أكثر من جزء ليكون ديناميكيًا أيضًا:<syntaxhighlight lang="twig">
يستطيع المرشح الديناميكي أن يعرِّف أكثر من جزء ليكون ديناميكيًا أيضًا:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('*_path_*', function ($name, $suffix, $arguments) {    // ...});
$filter = new \Twig\TwigFilter('*_path_*', function ($name, $suffix, $arguments) {    // ...});
</syntaxhighlight>يستقبل الفلتر كل قيم الجزء الديناميكي قبل وسائط الفلتر العادي، لكن بعد البيئة والسياق، فمثلًا، إذا استدعينا ‎<code>'foo'|a_path_b()</code>‎ فستُمرَّر الوسائط <code>('a', 'b', 'foo')</code> إلى الفلتر.
</syntaxhighlight>يستقبل المرشح كل قيم الجزء الديناميكي قبل وسائط المرشح العادي، لكن بعد البيئة والسياق، فمثلًا، إذا استدعينا ‎<code>'foo'|a_path_b()</code>‎ فستُمرَّر الوسائط <code>('a', 'b', 'foo')</code> إلى المرشح.


=== الفلاتر المهملة Deprecated Filters ===
=== المرشحات المهملة Deprecated Filters ===
تستطيع تحديد الفلتر ليكون مهملًا من خلال ضبط الخيار <code>deprecated</code> على <code>true</code>، كما تستطيع تحديد فلتر بديل يكون بديلًا للمهمل إذا رأيت ذلك:<syntaxhighlight lang="twig">
تستطيع تحديد المرشح ليكون مهملًا من خلال ضبط الخيار <code>deprecated</code> على <code>true</code>، كما تستطيع تحديد مرشح بديل يكون بديلًا للمهمل إذا رأيت ذلك:<syntaxhighlight lang="php">
$filter = new \Twig\TwigFilter('obsolete', function () {
$filter = new \Twig\TwigFilter('obsolete', function () {
     // ...
     // ...
}, ['deprecated' => true, 'alternative' => 'new_one']);
}, ['deprecated' => true, 'alternative' => 'new_one']);
</syntaxhighlight>عند إهمال الفلتر فإن Twig يرسل إشعار إهمال عند تصريف قالب يستخدم ذلك الفلتر، انظر صفحة [[Twig/recipes|الوصفات]] للمزيد.
</syntaxhighlight>عند إهمال المرشح فإن Twig يرسل إشعار إهمال عند تصريف قالب يستخدم ذلك المرشح، انظر صفحة [[Twig/recipes|الوصفات]] للمزيد.


== الدوال ==
== الدوال ==
تعرَّف الدوال بنفس طريقة تعريف الفلاتر، لكن تحتاج إلى إنشاء نسخة من ‎<code>\Twig\TwigFunction</code>:<syntaxhighlight lang="twig">
تعرَّف الدوال بنفس طريقة تعريف المرشحات، لكن تحتاج إلى إنشاء نسخة من ‎<code>\Twig\TwigFunction</code>:<syntaxhighlight lang="php">
$twig = new \Twig\Environment($loader);
$twig = new \Twig\Environment($loader);
$function = new \Twig\TwigFunction('function_name', function () {
$function = new \Twig\TwigFunction('function_name', function () {
سطر 178: سطر 182:
});
});
$twig->addFunction($function);
$twig->addFunction($function);
</syntaxhighlight>تدعم الدوال نفس المزايا التي تدعمها الفلاتر ما عدا خياري <code>pre_escape</code> و <code>preserves_safety</code>.
</syntaxhighlight>تدعم الدوال نفس المزايا التي تدعمها المرشحات ما عدا خياري <code>pre_escape</code> و <code>preserves_safety</code>.


== الاختبارات ==
== الاختبارات ==
تعَّرف الاختبارات بنفس طريقة تعريف الدوال والفلاتر، لكن تحتاج إلى إنشاء نسخة من ‎<code>\Twig\TwigTest</code>:<syntaxhighlight lang="twig">
تعَّرف الاختبارات بنفس طريقة تعريف الدوال والمرشحات، لكن تحتاج إلى إنشاء نسخة من ‎<code>\Twig\TwigTest</code>:<syntaxhighlight lang="php">
$twig = new \Twig\Environment($loader);
$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('test_name', function () {
$test = new \Twig\TwigTest('test_name', function () {
سطر 187: سطر 191:
});
});
$twig->addTest($test);
$twig->addTest($test);
</syntaxhighlight>تسمح لك الاختبارات بإنشاء منطق يختص بتطبيق ما من أجل تقييم الشرطيات البوليانية (boolean conditions). مثلًا، لتنشئ اختبار Twig يتحقق إن كانت الكائنات حمراء (red):<syntaxhighlight lang="twig">
</syntaxhighlight>تسمح لك الاختبارات بإنشاء منطق يختص بتطبيق ما من أجل تقييم الشرطيات البوليانية boolean conditions. مثلًا، لتنشئ اختبار Twig يتحقق إن كانت الكائنات حمراء red:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
$twig = new \Twig\Environment($loader);
$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('red', function ($value) {
$test = new \Twig\TwigTest('red', function ($value) {
سطر 210: سطر 203:
});
});
$twig->addTest($test);
$twig->addTest($test);
</syntaxhighlight>يجب أن تعيد دوال الاختبار إما <code>true</code> أو <code>false</code>.
</syntaxhighlight>يجب أن تعيد دوال الاختبار إما <code>true</code> أو <code>false</code>.  


استخدم الخيار <code>node_class</code> عند إنشاء الاختبارات من أجل توفير تجميعة مخصصة للاختبار، وذلك ينفعك إن كان يمكن تصريف اختبارك داخل أنوع البيانات الأولية لـ PHP، وتستخدمه اختبارات عديدة مضمنة في Twig:<syntaxhighlight lang="twig">
استخدم الخيار <code>node_class</code> عند إنشاء الاختبارات من أجل توفير تجميعة مخصصة للاختبار، وذلك ينفعك إن كان يمكن تصريف اختبارك داخل أنوع البيانات الأولية لـ PHP، وتستخدمه اختبارات عديدة مضمنة في Twig:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace App;
namespace App;


سطر 263: سطر 231:
     }
     }
}
}
</syntaxhighlight>يوضح المثال أعلاه كيف تنشئ اختبارات تستخدم صنف العقدة (node class) الذي يكون له وصول إلى عقدة فرعية اسمها <code>node</code>، تحتوي على القيمة التي تُختبر.<syntaxhighlight lang="twig">
</syntaxhighlight>يوضح المثال أعلاه كيف تنشئ اختبارات تستخدم صنف العقدة node class الذي يكون له وصول إلى عقدة فرعية اسمها <code>node</code>، تحتوي على القيمة التي تُختبر.<syntaxhighlight lang="twig">
{% if my_value is odd %}
{% if my_value is odd %}
</syntaxhighlight>إذا استُخدم فلتر <code>odd</code> في شيفرة مثل التي أعلاه فإن عقدة <code>node</code> ستحتوي على تعبير لـ <code>my_value</code>، ويكون للاختبارات المبنية على عقد وصول أيضًا إلى عقدة <code>arguments</code>، وستحتوي تلك العقدة على وسائط أخرى عديدة تم توفيرها لاختبارك.
</syntaxhighlight>إذا استُخدم مرشح <code>odd</code> في شيفرة مثل التي أعلاه فإن عقدة <code>node</code> ستحتوي على تعبير لـ <code>my_value</code>، ويكون للاختبارات المبنية على عقد وصول أيضًا إلى عقدة <code>arguments</code>، وستحتوي تلك العقدة على وسائط أخرى عديدة تم توفيرها لاختبارك.


وإذا أردت تمرير عددًا متغيرًا من الوسائط الموضعية (positional) أو المسماة (named) إلى الاختبار فاضبط خيار <code>is_variadic</code> على <code>true</code>، إذ أن الاختبارات تدعم الأسماء الديناميكية (انظر الفلاتر الديناميكية للبنية اللغوية (Syntax)).
وإذا أردت تمرير عددًا متغيرًا من الوسائط الموضعية positional أو المسماة named إلى الاختبار فاضبط خيار <code>is_variadic</code> على <code>true</code>، إذ أن الاختبارات تدعم الأسماء الديناميكية انظر المرشحات الديناميكية للبنية اللغوية Syntax.


== الوسوم ==
== الوسوم ==
سطر 273: سطر 241:


* إذا كان وسمك يولد بعض المخرجات فاستخدم دالة.
* إذا كان وسمك يولد بعض المخرجات فاستخدم دالة.
* إذا كان وسمك يعدل بعض المحتوى ويعيده فاستخدم فلترًا.
* إذا كان وسمك يعدل بعض المحتوى ويعيده فاستخدم مرشحًا.


مثلًا، إذا أردت إنشاء وسم يحول نصًا بصيغة مارك داون إلى HTML، فأنشئ فلتر <code>markdown</code> بدلًا من إنشاء وسم:<syntaxhighlight lang="twig">
مثلًا، إذا أردت إنشاء وسم يحول نصًا بصيغة مارك داون إلى HTML، فأنشئ مرشح <code>markdown</code> بدلًا من إنشاء وسم:<syntaxhighlight lang="twig">
{{ '**markdown** text'|markdown }}
{{ '**markdown** text'|markdown }}
</syntaxhighlight>إذا أردت استخدام هذا الفلتر على نصوص كثيرة فغلفه بوسم [[Twig/apply|apply]]:<syntaxhighlight lang="twig">
</syntaxhighlight>إذا أردت استخدام هذا المرشح على نصوص كثيرة فغلفه بوسم [[Twig/apply|<code>apply</code>]]:<syntaxhighlight lang="twig">
{% apply markdown %}
{% apply markdown %}
Title
Title
سطر 284: سطر 252:
Much better than creating a tag as you can **compose** filters.
Much better than creating a tag as you can **compose** filters.
{% endapply %}
{% endapply %}
</syntaxhighlight>إذا لم يكن وسمك يخرج أي شيء لكنه موجود بسبب أثر جانبي فأنشئ دالة لا تعيد شيئًا واستدعها من خلال وسم <code>filter</code>، فمثلًا إذا أردت إنشاء وسم يسجل النصوص فأنشئ دالة <code>log</code> واستدعها من خلال وسم <code>do</code>:<syntaxhighlight lang="twig">
</syntaxhighlight>إذا لم يكن وسمك يخرج أي شيء لكنه موجود بسبب أثر جانبي فأنشئ دالة لا تعيد شيئًا واستدعها من خلال وسم <code>[[Twig/filter|filter]]</code>، فمثلًا إذا أردت إنشاء وسم يسجل النصوص فأنشئ دالة <code>log</code> واستدعها من خلال وسم <code>[[Twig/do|do]]</code>:<syntaxhighlight lang="twig">
{% do log('Log some things') %}
{% do log('Log some things') %}
</syntaxhighlight>والآن لننشئ وسمًا لبُنيةٍ لغوية جديدة وليكن <code>set</code> الذي يسمح بتعريف متغيرات بسيطة من داخل القالب، ويمكن استخدام ذلك الوسم كما يلي:<syntaxhighlight lang="twig">
</syntaxhighlight>والآن لننشئ وسمًا لبُنيةٍ لغوية جديدة وليكن <code>[[Twig/set|set]]</code> الذي يسمح بتعريف متغيرات بسيطة من داخل القالب، ويمكن استخدام ذلك الوسم كما يلي:<syntaxhighlight lang="twig">
{% set name = "value" %}
{% set name = "value" %}


سطر 292: سطر 260:


{# should output value #}
{# should output value #}
</syntaxhighlight>لاحظ أن وسم <code>set</code> جزء من توسيع <code>core</code> وعليه سيكون متاحًا دائمًا، والنسخة المضمنة منه أقوى وتدعم إسنادات متعددة افتراضيًا. ستحتاج إلى ثلاثة خطوات من أجل تعريف وسم جديد:
</syntaxhighlight>لاحظ أن وسم [[Twig/set|<code>set</code>]] جزء من توسيع النواة <code>core</code> وعليه سيكون متاحًا دائمًا، والنسخة المضمنة منه أقوى وتدعم إسنادات متعددة افتراضيًا. ستحتاج إلى ثلاثة خطوات من أجل تعريف وسم جديد:


* تعريف صنف محلل للرموز (token parser class) مسؤول عن تحليل شيفرة القالب.
* تعريف صنف محلل للوحدات token parser class مسؤول عن تحليل شيفرة القالب.
* تعريف صنف عقدة يكون مسؤولًا عن تحويل الشيفرة المحلَّلة إلى PHP.
* تعريف صنف عقدة يكون مسؤولًا عن تحويل الشيفرة المحلَّلة إلى PHP.
* تسجيل الوسم.
* تسجيل الوسم.
سطر 302: سطر 270:
$twig = new \Twig\Environment($loader);
$twig = new \Twig\Environment($loader);
$twig->addTokenParser(new Project_Set_TokenParser());
$twig->addTokenParser(new Project_Set_TokenParser());
</syntaxhighlight>لننظر الآن في الشيفرة الحقيقية لهذا الصنف:<syntaxhighlight lang="twig">
</syntaxhighlight>
1
 
2
=== تعريف محلل مفتاح Token Parser  ===
3
لننظر الآن في الشيفرة الحقيقية لهذا الصنف:<syntaxhighlight lang="php">
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Project_Set_TokenParser extends \Twig\TokenParser\AbstractTokenParser
class Project_Set_TokenParser extends \Twig\TokenParser\AbstractTokenParser
{
{
سطر 343: سطر 294:
     }
     }
}
}
</syntaxhighlight>يجب أن يعيد التابع <code>getTag()‎</code> الوسم الذي نريد تحليله، وهو <code>set</code> هنا، ويُستدعى التابع <code>parse()‎</code> كلما وجد وسم <code>set</code>، ويجب أن يعيد نسخة من ‎<code>\Twig\Node\Node</code> تمثل العقدة (ستجد شرح إنشاء استدعاءات <code>Project_Set_Node</code> لاحقًا في الصفحة).
</syntaxhighlight>يجب أن يعيد التابع <code>getTag()‎</code> الوسم الذي نريد تحليله، وهو [[Twig/set|<code>set</code>]] هنا، ويُستدعى التابع <code>parse()‎</code> كلما وجد وسم <code>[[Twig/set|set]]</code>، ويجب أن يعيد نسخة من ‎<code>\Twig\Node\Node</code> تمثل العقدة (ستجد شرح إنشاء استدعاءات <code>Project_Set_Node</code> لاحقًا في الصفحة).


وتُبسَّط عملية التحليل بفضل مجموعة من التوابع التي يمكنك استدعاءها من مجرى الرموز (token stream) ‎<code>$this->parser->getStream()‎</code>:
وتُبسَّط عملية التحليل بفضل مجموعة من التوابع التي يمكنك استدعاءها من مجرى مفتاح التشفير token stream أي ‎<code>$this->parser->getStream()‎</code>:


* <code>getCurrent()‎</code>: تجلب الرمز الحالي في المجرى.
* <code>getCurrent()‎</code>: تجلب المفتاح token الحالي في المجرى.


* <code>next()‎</code>: تتحرك إلى الرمز التالي في المجرى لكن تعيد الرمز القديم.
* <code>next()‎</code>: تتحرك إلى المفتاح التالي في المجرى لكن تعيد المفتاح القديم.
* <code>test($type)</code> أو  <code>test($value)</code> أو <code>test($type, $value)</code>: تحدد ما إذا كان الرمز (token) الحالي من نوع أو قيمة بعينها أو كليهما، وقد تكون القيمة مصفوفة من عدة قيم ممكنة.
* <code>test($type)</code> أو  <code>test($value)</code> أو <code>test($type, $value)</code>: تحدد ما إذا كان المفتاح الحالي من نوع أو قيمة بعينها أو كليهما، وقد تكون القيمة مصفوفة من عدة قيم ممكنة.
* <code>expect($type[, $value[, $message]])</code>: إذا لم يكن الرمز الحالي من النوع المعطى أو القيمة المعطاة فيُرفع خطأ لغوي (syntax error)، وإلا إذا كان النوع صحيحًا وكذلك القيمة فيعاد الرمز وينتقل المجرى إلى الرمز التالي.
* <code>expect($type[, $value[, $message]])</code>: إذا لم يكن المفتاح الحالي من النوع المعطى أو القيمة المعطاة فيُرفع خطأ لغوي syntax error، وإلا إذا كان النوع صحيحًا وكذلك القيمة فيعاد المفتاح وينتقل المجرى إلى الوحدة التالية.
* <code>look()‎</code>: ينظر إلى الرمز التالي من غير استهلاكه.
* <code>look()‎</code>: ينظر إلى الوحدة التالية من غير استهلاكه.
* يتم تحليل التعابير من خلال استدعاء  <code>parseExpression()‎</code> كما فعلنا في وسم <code>set</code>.
* يتم تحليل التعابير من خلال استدعاء  <code>parseExpression()‎</code> كما فعلنا في وسم <code>set</code>.


تستطيع تعلم جميع التفاصيل التي في عملية التحليل بقراءة أصناف <code>TokenParser</code> الموجودة فعليًا.
تستطيع تعلم جميع التفاصيل التي في عملية التحليل بقراءة أصناف <code>TokenParser</code> الموجودة فعليًا.


=== تعريف العقدة ===
=== تعريف عقدة ===
إن الصنف  <code>Project_Set_Node</code> نفسه قصير نوعًا ما:<syntaxhighlight lang="twig">
إن الصنف  <code>Project_Set_Node</code> نفسه قصير نوعًا ما:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Project_Set_Node extends \Twig\Node\Node
class Project_Set_Node extends \Twig\Node\Node
{
{
سطر 402: سطر 336:
* <code>addDebugInfo()‎</code>: يضيف السطر المتعلق بالعقدة الحالية من ملف القالب الأصلي كتعليق.
* <code>addDebugInfo()‎</code>: يضيف السطر المتعلق بالعقدة الحالية من ملف القالب الأصلي كتعليق.
* <code>indent()‎</code>: يزيح الشيفرة المولَّدة، انظر ‎<code>\Twig\Node\BlockNode</code> لمثال على الاستخدام.
* <code>indent()‎</code>: يزيح الشيفرة المولَّدة، انظر ‎<code>\Twig\Node\BlockNode</code> لمثال على الاستخدام.
* <code>outdent()‎</code>: يزيح الشيفرة المولَّدة خارجيًا (outdent)، انظر  ‎<code>\Twig\Node\BlockNode</code> لمثال على الاستخدام.
* <code>outdent()‎</code>: يزيح الشيفرة المولَّدة خارجيًا outdent ، انظر  ‎<code>\Twig\Node\BlockNode</code> لمثال على الاستخدام.


== إنشاء توسيع ==
== إنشاء توسعة Extension  ==
إن ما يجعلك تنشئ توسيعًا هو نقل الشيفرة المستخدمة بكثرة إلى صنف قابل لإعادة استخدامه مثل إضافة دعم للدُوَلية مثلًا (internationalization)، بأن يكون التطبيق مستخدمًا في عدة دول بعدة لغات مختلفة. ويستطيع التوسيع أن يعرِّف الوسوم والفلاتر والاختبارات والعوامل والدوال وزوار العقد (node visitors).
إن ما يجعلك تنشئ توسعة هو نقل الشيفرة المستخدمة بكثرة إلى صنف قابل لإعادة استخدامه مثل إضافة دعم للدُوَلية مثلًا internationalization، بأن يكون التطبيق مستخدمًا في عدة دول بعدة لغات مختلفة. ويستطيع التوسيع أن يعرِّف الوسوم والمرشحات والاختبارات والعوامل والدوال وزوار العقد node visitors .


ومن المفيد غالبًا أن تنشئ توسيعًا واحدًا لمشروعك لاستضافة جميع الوسوم المحددة والفلاتر التي تريد إضافتها إلى Twig. كذلك، عند تحزيم شيفرتك في توسيع فإن Twig من الذكاء بحيث يعيد تصريف قوالبك كلما أجريت تغييرًا فيه، إذا كان <code>auto_reload</code> مفعلًا.  
ومن المفيد غالبًا أن تنشئ توسعة واحدة لمشروعك لاستضافة جميع الوسوم المحددة والمرشحات التي تريد إضافتها إلى Twig. كذلك، عند تحزيم شيفرتك في توسيع فإن Twig من الذكاء بحيث يعيد تصريف قوالبك كلما أجريت تغييرًا فيه، إذا كان <code>auto_reload</code> مفعلًا.  


التوسيع (Extension) هو صنف يطبق الواجهة التالية:<syntaxhighlight lang="twig">
التوسعة Extension هي صنف يطبق الواجهة التالية:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
interface \Twig\Extension\ExtensionInterface
interface \Twig\Extension\ExtensionInterface
{
{
     /**
     /**
     * يعيد نُسخ محلل الرموز لإضافته إلى القائمة الحالية.
     * يعيد نسخ محلل الرموز لإضافته إلى القائمة الحالية
     *
     *
     * @return \Twig\TokenParser\TokenParserInterface[]
     * @return \Twig\TokenParser\TokenParserInterface[]
سطر 464: سطر 354:


     /**
     /**
     * يعيد نسخ زائد العقدة لإضافتها إلى القائمة الحالية.
     * يعيد نسخ زائد العقدة لإضافتها إلى القائمة الحالية
     *
     *
     * @return \Twig\NodeVisitor\NodeVisitorInterface[]
     * @return \Twig\NodeVisitor\NodeVisitorInterface[]
سطر 471: سطر 361:


     /**
     /**
     * يعيد قائمة من الفلاتر لإضافتها إلى القائمة الحالية.
     * يعيد قائمة من الفلاتر لإضافتها إلى القائمة الحالية
     *
     *
     * @return \Twig\TwigFilter[]
     * @return \Twig\TwigFilter[]
سطر 478: سطر 368:


     /**
     /**
     * يعيد قائمة من الاختبارات لإضافتها إلى القائمة الحالية.
     * يعيد قائمة من الاختبارات لإضافتها إلى القائمة الحالية
     *
     *
     * @return \Twig\TwigTest[]
     * @return \Twig\TwigTest[]
سطر 485: سطر 375:


     /**
     /**
     * يعيد قائمة من الدوال لإضافتها إلى القائمة الحالية.
     * يعيد قائمة من الدوال لإضافتها إلى القائمة الحالية
     *
     *
     * @return \Twig\TwigFunction[]
     * @return \Twig\TwigFunction[]
سطر 499: سطر 389:
     public function getOperators();
     public function getOperators();
}
}
</syntaxhighlight>إذا أردت الحفاظ على صنف التوسيع الخاص بك رشيقًا فاجعله يرث من الصنف المضمن ‎<code>\Twig\Extension\AbstractExtension</code> بدلًا من تطبيق الواجهة بما أنه يوفر تطبيقات فارغة لجميع التوابع:<syntaxhighlight lang="twig">
</syntaxhighlight>إذا أردت الحفاظ على صنف التوسيع الخاص بك رشيقًا فاجعله يرث من الصنف المضمن ‎<code>\Twig\Extension\AbstractExtension</code> بدلًا من تطبيق الواجهة بما أنه يوفر تطبيقات فارغة لجميع التوابع:<syntaxhighlight lang="php">
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
{
}
}
</syntaxhighlight>لا يفعل هذا التوسيع أي شيء إلى الآن، وسنخصصه فيما يلي من الشرح، وتستطيع حفظه في أي مكان في نظام الملفات بما أن جميع التوسيعات يجب أن تُسجل صراحة لتكون متاحة في قوالبك، كما تستطيع تسجيل توسيع باستخدام التابع <code>addExtension()‎</code> على الكائن الرئيسي <code>Enivronment</code> الخاص بك: <syntaxhighlight lang="twig">
</syntaxhighlight>لا تفعل هذه التوسعة أي شيء إلى الآن، وسنخصصها فيما يلي من الشرح، وتستطيع حفظها في أي مكان في نظام الملفات بما أن جميع التوسعات يجب أن تُسجل صراحة لتكون متاحة في قوالبك، كما تستطيع تسجيل توسيع باستخدام التابع <code>addExtension()‎</code> على الكائن الرئيسي <code>Enivronment</code> الخاص بك: <syntaxhighlight lang="php">
$twig = new \Twig\Environment($loader);
$twig = new \Twig\Environment($loader);
$twig->addExtension(new Project_Twig_Extension());
$twig->addExtension(new Project_Twig_Extension());
</syntaxhighlight>تستطيع النظر في توسيعات <code>core</code> الخاصة بـ Twig إذ هي أمثلة ممتازة على كيفية عمل التوسيعات.
</syntaxhighlight>تستطيع النظر في توسعات النواة <code>core</code> الخاصة بـ Twig إذ هي أمثلة ممتازة على كيفية عمل التوسعات.


=== المتغيرات العامة ===
=== المتغيرات العامة ===
يمكن تسجيل المتغيرات العامة في توسيع من خلال التابع <code>getGlobals()‎</code> :<syntaxhighlight lang="twig">
يمكن تسجيل المتغيرات العامة في توسعة من خلال التابع <code>getGlobals()‎</code>:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
{
{
سطر 535: سطر 414:


=== الدوال ===
=== الدوال ===
يمكن تسجيل الدوال في توسيع من خلال التابع <code>getFunctions()‎</code> :<syntaxhighlight lang="twig">
يمكن تسجيل الدوال في توسعة من خلال التابع <code>getFunctions()‎</code> :<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
{
سطر 560: سطر 428:
</syntaxhighlight>
</syntaxhighlight>


=== الفلاتر ===
=== المرشحات ===
إذا أردت إضافة فلتر إلى توسيع فستحتاج إلى تخطي التابع <code>getFilters()‎</code>، فيجب أن يعيد هذا التابع مصفوفة من الفلاتر لإضافتها إلى بيئة Twig:<syntaxhighlight lang="twig">
إذا أردت إضافة مرشح إلى توسعة فستحتاج إلى إعادة تعريف التابع <code>getFilters()‎</code>، فيجب أن يعيد هذا التابع مصفوفة من المرشحات لإضافتها إلى بيئة Twig:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
{
سطر 587: سطر 444:


=== الوسوم ===
=== الوسوم ===
يمكن إضافة الوسوم إلى توسيع بتخطي (override) التابع <code>getTokenParsers()‎</code> ، فيجب أن يعيد مصفوفةً من الوسوم لإضافتها إلى بيئة Twig:<syntaxhighlight lang="twig">
يمكن إضافة الوسوم إلى توسعة بإعادة تعريف override التابع <code>getTokenParsers()‎</code>، فيجب أن يعيد مصفوفةً من الوسوم لإضافتها إلى بيئة Twig:<syntaxhighlight lang="php">
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
{
سطر 597: سطر 454:
     // ...
     // ...
}
}
</syntaxhighlight>أضفنا في الشيفرة السابقة وسمًا واحدًا جديدًا، عرَّفه الصنف <code>Project_Set_TokenParser</code> ، وهذا الصنف مسؤول عن تحليل الوسم وتصريفه إلى PHP.
</syntaxhighlight>أضفنا في الشيفرة السابقة وسمًا واحدًا جديدًا، عرَّفه الصنف <code>Project_Set_TokenParser</code>، وهذا الصنف مسؤول عن تحليل الوسم وتصريفه إلى [[PHP/Array|PHP]].


=== العوامل (Operators) ===
=== العوامل Operators ===
يسمح لك التابع  <code>getOperators()‎</code> بإضافة عوامل جديدة، انظر الشيفرة التالية كمثال على كيفية إضافة العوامل <code>!</code> و <code>||</code> و <code>&&</code>:<syntaxhighlight lang="twig">
يسمح لك التابع  <code>getOperators()‎</code> بإضافة عوامل جديدة، انظر الشيفرة التالية كمثال على كيفية إضافة العوامل <code>!</code> و <code>||</code> و <code>&&</code>:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
{
سطر 638: سطر 478:


=== الاختبارات ===
=== الاختبارات ===
يسمح لك التابع  <code>getTests()‎</code> بإضافة دوال اختبار جديدة:<syntaxhighlight lang="twig">
يسمح لك التابع  <code>getTests()‎</code> بإضافة دوال اختبار جديدة:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
{
سطر 664: سطر 493:


=== موازنة بين التعريف ووقت التشغيل ===
=== موازنة بين التعريف ووقت التشغيل ===
يمكن تعريف تطبيقات وقت التشغيل لفلاتر Twig ودواله واختباراته كأي نوع بيانات "callable" في PHP:
يمكن تعريف تنفيذات وقت التشغيل runtime implementations لمرشحات Twig ودواله واختباراته كأي نوع بيانات "[[PHP/callable|callable]]" في PHP:


* '''الدوال/التوابع الساكنة''': بسيطة في تطبيقها، وسريعة -تستخدمها جميع توسيعات <code>core</code> في Twig، لكن من الصعب أن يعتمد وقت التشغيل (runtime) على كائنات خارجية.
* '''الدوال/التوابع الساكنة''': بسيطة في تطبيقها، وسريعة -تستخدمها جميع توسعات <code>core</code> في Twig، لكن من الصعب أن يعتمد وقت التشغيل runtime على كائنات خارجية.
* '''الإغلاقات (closures)''': بسيطة في تطبيقها.
* '''الإغلاقات closures''' : بسيطة في تنفيذها.
* '''توابع الكائن''': أكثر مرونة وتحتاجها إن كانت شيفرة وقت التشغيل لديك تعتمد على كائنات خارجية.
* '''توابع الكائن''': أكثر مرونة وتحتاجها إن كانت شيفرة وقت التشغيل لديك تعتمد على كائنات خارجية.


إن أبسط طريقة لاستخدام التوابع هي تعريفها على التوسيع نفسه:<syntaxhighlight lang="twig">
إن أبسط طريقة لاستخدام التوابع هي تعريفها على توسعة نفسها:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
{
سطر 715: سطر 523:
</syntaxhighlight>هذه الطريقة على بساطتها إلا أنه لا يُنصح بها لأنها تجعل تصريف القالب يعتمد على اعتماديات وقت التشغيل حتى لو لم تكن ثمة حاجة إليها.
</syntaxhighlight>هذه الطريقة على بساطتها إلا أنه لا يُنصح بها لأنها تجعل تصريف القالب يعتمد على اعتماديات وقت التشغيل حتى لو لم تكن ثمة حاجة إليها.


تستطيع فصل تعريفات التوسيع من تطبيقات وقت تشغيلها بتسجيل نسخة من ‎<code>\Twig\RuntimeLoader\RuntimeLoaderInterface</code> على البيئة التي تعرف كيفية بدأ أصناف وقت التشغيل تلك، ويجب أن تكون أصناف وقت التشغيل قابلة للتحميل التلقائي:<syntaxhighlight lang="twig">
تستطيع فصل تعريفات توسعة من تنفيذات وقت تشغيلها بتسجيل نسخة من ‎<code>\Twig\RuntimeLoader\RuntimeLoaderInterface</code> على البيئة التي تعرف كيفية بدء أصناف وقت التشغيل تلك، ويجب أن تكون أصناف وقت التشغيل قابلة للتحميل التلقائي:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RuntimeLoader implements \Twig\RuntimeLoader\RuntimeLoaderInterface
class RuntimeLoader implements \Twig\RuntimeLoader\RuntimeLoaderInterface
{
{
سطر 749: سطر 541:


$twig->addRuntimeLoader(new RuntimeLoader());
$twig->addRuntimeLoader(new RuntimeLoader());
</syntaxhighlight>لاحظ أن Twig يأتي بمحمِّل وقت تشغيل ‎<code>\Twig\RuntimeLoader\ContainerRuntimeLoader</code> متوافق مع PSR-11، وصار من الممكن أن تنقل منطق وقت التشغيل إلى صنف <code>Project_Twig_RuntimeExtension</code> جديد وتستخدمه مباشرة في التوسيع:<syntaxhighlight lang="twig">
</syntaxhighlight>لاحظ أن Twig يأتي بمحمِّل وقت تشغيل ‎<code>\Twig\RuntimeLoader\ContainerRuntimeLoader</code> متوافق مع PSR-11، وصار من الممكن أن تنقل منطق وقت التشغيل إلى صنف <code>Project_Twig_RuntimeExtension</code> جديد وتستخدمه مباشرة في التوسيع:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Project_Twig_RuntimeExtension
class Project_Twig_RuntimeExtension
{
{
سطر 804: سطر 570:
</syntaxhighlight>
</syntaxhighlight>


== اختبار التوسيع ==
== اختبار توسعة ==


=== الاختبارات الدالّية (Functional Tests) ===
=== الاختبارات الدالية  ===
تستطيع إنشاء اختبارات دالّية للتوسيعات من خلال إنشاء بنية الملف التالية في مجلد اختبارك:<syntaxhighlight lang="twig">
تستطيع إنشاء اختبارات دالّية Functional Tests للتوسعات من خلال إنشاء بنية الملف التالية في مجلد اختبارك:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
Fixtures/
Fixtures/
     filters/
     filters/
سطر 830: سطر 585:
         bar.test
         bar.test
IntegrationTest.php
IntegrationTest.php
</syntaxhighlight>ويجب أن يبدو ملف  <code>IntegrationTest.php</code> كما يلي:<syntaxhighlight lang="twig">
</syntaxhighlight>ويجب أن يبدو ملف  <code>IntegrationTest.php</code> كما يلي:<syntaxhighlight lang="php">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Twig\Test\IntegrationTestCase;
use Twig\Test\IntegrationTestCase;


سطر 865: سطر 603:
     }
     }
}
}
</syntaxhighlight>تستطيع العثور على أمثلة لواجهات الاختبار (Fixtures) داخل مستودع Twig، داخل مجلد [https://github.com/twigphp/Twig/tree/3.x/tests/Fixtures tests/Twig/Fixtures].
</syntaxhighlight>تستطيع العثور على أمثلة لواجهات الاختبار Fixtures داخل مستودع Twig، داخل مجلد [https://github.com/twigphp/Twig/tree/3.x/tests/Fixtures tests/Twig/Fixtures].


=== اختبارات العُقد ===
=== اختبارات العقد ===
قد يكون اختبار زوار العقد معقدًا، لذلك وسِّع حالات اختبارك من ‎<code>\Twig\Test\NodeTestCase</code>، وستجد أمثلة في مستودع Twig، داخل مجلد [https://github.com/twigphp/Twig/tree/3.x/tests/Node tests/Twig/Node].
قد يكون اختبار زوار العقد معقدًا، لذلك اجلب حالات اختبارك عبر الوراثة من ‎<code>\Twig\Test\NodeTestCase</code>، وستجد أمثلة في مستودع Twig، داخل مجلد [https://github.com/twigphp/Twig/tree/3.x/tests/Node tests/Twig/Node].


== انظر أيضًا ==
== انظر أيضًا ==


* [[Twig/templates|Twig لمصممي القوالب]].
* [[Twig/templates|Twig لمصممي القوالب]]
* [[Twig/api|Twig للمطورين]].
* [[Twig/api|Twig للمطورين]]
* [[Twig/internals|المكونات الداخلية لمحرك القوالب Twig]].
* [[Twig/internals|المكونات الداخلية لمحرك القوالب Twig]]


== المصادر ==
== المصادر ==


* [https://twig.symfony.com/doc/3.x/advanced.html#environment-aware-filters صفحة توسيع Twig من توثيق Twig الرسمي].
* [https://twig.symfony.com/doc/3.x/advanced.html#environment-aware-filters صفحة Extending Twig من توثيق Twig الرسمي]
 
[[تصنيف:Twig]]
[[تصنيف:Twig]]
[[تصنيف:Extending Twig]]
[[تصنيف:Twig Intro]]
[[تصنيف:Template Engine]]
[[تصنيف:Filters]]
[[تصنيف:Escaping]]
[[تصنيف:Tags]]

المراجعة الحالية بتاريخ 10:55، 7 أكتوبر 2022

توسيع Twig يتم بعدة طرائق، إذ تستطيع إضافة وسوم tags إضافية ومرشحات واختبارات وعوامل operators ومتغيرات عامة global variables ودوال أيضًا، بل تستطيع توسيع المحلل parser نفسه بزوار العقد node visitors. لاحظ أن أول جزء من هذه الصفحة يشرح كيفية توسيع عمل محرك Twig، فإذا أردت إعادة استخدام ما تفعله في مشاريع مختلفة أو كنت تريد مشاركتها مع غيرك فيجب أن تنشئ توسيعًا بنفسك كما هو موضح أدناه.

وانتبه إلى أن توسيع Twig من غير إنشاء توسعة جديدة extension تجعل Twig غير قادر على إعادة تصريف recompile قوالبك عند تحديث شيفرة PHP، ويجب أن تعطل تخزين القوالب المؤقت template caching أو تحزِّم شيفرتك في توسيعة كي ترى تغييراتك في الوقت الحقيقي، كما سيلي بيانه لاحقًا في هذه الصفحة.

يجب كذلك أن تفهم أولًا الاختلافات بين نقاط التوسعة extension points المختلفة ومتى يجب استخدام كل منها، قبل توسيع Twig نفسه، فاعلم أن Twig به بنيتين لغويتين أساسيتين:

  • {{ }}: تُستخدم لطباعة نتيجة تقييم التعبير.
  • {% %}: تُستخدم لتنفيذ التعليمات.

انظر كيفية تنفيذ مولد النص التلقائي lorem ipsum -الذي يحتاج أن يعرف عدد الكلمات التي يجب توليدها- كي تفهم كيف يكشف Twig كثيرًا من نقاط التوسعة، استخدم وسم lipsum:

{% lipsum 40 %}

هذا وإن كان يعمل إلا أن استخدام وسمًا لـ lipsum ليس الخيار الأمثل لثلاثة أسباب رئيسية:

  • lipsum ليس بنية لغوية.
  • الوسم له مخرجات.
  • الوسم ليس مرنًا إذ لا تستطيع استخدامه في تعبير:
{{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}

ولا تحتاج إلى إنشاء وسوم إلا نادرًا، وهذا أمر حسن لأنها أعقد نقاط التوسعة. لنستخدم الآن مرشح lipsum:

{{ 40|lipsum }}

وهذا يعمل أيضًا لكن يجب أن يحول المرشحُ القيمةَ الممرَّرة إلى شيء آخر، وهنا نستخدم القيمة للإشارة إلى عدد الكلمات التي يجب توليدها، لذا فإن 40 هي وسيط argument للمرشح، وليست القيمة التي نريد تحويلها. لنستخدم الآن دالة lipsum:

{{ lipsum(40) }}

ولهذا المثال خاصة فإن نقطة التوسعة التي ستُستخدم هي إنشاء الدالة، وتستطيع استخدامها في أي مكان يقبل تعبيرًا:

{{ 'some text' ~ lipsum(40) ~ 'some more text' }}

{% set lipsum = lipsum(40) %}

وأخيرًا تستطيع استخدام كائنًا عامًا global object مع تابع قادر على إنشاء نص لوريم إبسوم:

{{ text.lipsum(40) }}

وكقاعدة عامة، استخدم الدوال في حالة المزايا التي يكثر استخدامها، والكائنات العامة لأي شيء آخر، واعتبر بالجدول التالي عند توسيع Twig:

طرق توسيع Twig وحالات استخدامها
الطريقة صعوبة التنفيذ التواتر متى
شيفرة جامعة macro بسيط متكرر إنشاء المحتوى
عام global بسيط متكرر كائن مساعد
دالة function بسيط متكرر تَحوُّل القيمة
وسم tag معقد نادر بنية خاصة للغة Domain Specific Language
اختبار test بسيط نادر إجراء بولياني
عامل operator بسيط نادر تَحوُّل القيمة

المتغيرات العامة Global Variables

المتغير العام يشبه أي متغير قالب آخر إلا أنه متاح في جميع القوالب والشيفرات الجامعة:

$twig = new \Twig\Environment($loader);
$twig->addGlobal('text', new Text());

تستطيع حينها استخدام متغير text في أي مكان داخل القالب:

{{ text.lipsum(40) }}

المرشحات

إنشاء المرشح يكون بربط اسم مع نوع البيانات "callable" من PHP:

// دالة مجهولة
$filter = new \Twig\TwigFilter('rot13', function ($string) {
    return str_rot13($string);
});

// بسيطة PHP أو دالة
$filter = new \Twig\TwigFilter('rot13', 'str_rot13');

// class static method أو تابع صنف ساكن
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
$filter = new \Twig\TwigFilter('rot13', 'SomeClass::rot13Filter');

// أو تابع صنف
$filter = new \Twig\TwigFilter('rot13', [$this, 'rot13Filter']);
// هذا الأخير يحتاج إلى تطبيق في وقت التشغيل، انظر أدناه للتوضيح.
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);

يكون أول وسيط ممرَّر إلى منشئ ‎\Twig\TwigFilter هو اسم المرشح الذي ستستخدمه في القوالب والثاني هو نوع البيانات "callable" المرتبط به. ثم بعد ذلك، نضيف المرشح إلى بيئة Twig:

$twig = new \Twig\Environment($loader);
$twig->addFilter($filter);

انظر الآن كيف تستخدمه في قالب:

{{ 'Twig'|rot13 }}

{# Gjvt سيُخرج #}

يستقبل نوع البيانات "callable" (ردود النداء) الجانب الأيسر من المرشح قبل الأنبوب | كوسيط أول عند استدعاء Twig له، وتمرَّر وسائط إضافية إلى المرشح داخل أقواس (). فمثلًا تُصرَّف الشيفرة التالية:

{{ 'TWIG'|lower }}
{{ now|date('d/m/Y') }}

لتكون كما يلي بعد التصريف:

<?php echo strtolower('TWIG') ?>
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>

يأخذ الصنف ‎\Twig\TwigFilter مصفوفة من الخيارات كآخر وسيط له:

$filter = new \Twig\TwigFilter('rot13', 'str_rot13', $options);

المرشحات الواعية للبيئة

إذا أردت الوصول إلى نسخة البيئة الحالية في مرشحك فاجعل الخيار needs_environment على true، وسيمرر Twig البيئة الحالية كأول وسيط إلى استدعاء المرشح:

$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $string) {
    // اجلب مجموعة المحارف الحالية للنسخة.
    $charset = $env->getCharset();

    return str_rot13($string);
}, ['needs_environment' => true]);

المرشحات الواعية للسياق

إذا أردت الوصول إلى السياق الحالي في مرشحك فاجعل الخيار needs_context على true، وسيمرر Twig السياق الحالي كأول وسيط إلى استدعاء المرشح أو الوسيط الثاني إذا كان needs_environment على true أيضًا:

$filter = new \Twig\TwigFilter('rot13', function ($context, $string) {
    // ...
}, ['needs_context' => true]);

$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $context, $string) {
    // ...
}, ['needs_context' => true, 'needs_environment' => true]);

التهريب التلقائي Automatic Escaping

إذا كان التهريب التلقائي مفعلًا فقد يُهرَّب خرج المرشح قبل الطباعة، وإن كان مرشحك يتصرف كمهرِّب أو يخرج شيفرة HTML أو جافاسكربت فربما تريد طباعة الخرج الخام، فاضبط خيار is_safe في مثل تلك الحالة:

$filter = new \Twig\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);

قد تحتاج بعض المرشحات إلى التعامل مع مدخلات مهرَّبة سلفًا أو آمنة، كما في حالة إضافة وسوم HTML آمنة إلى خرج غير آمن، ففي مثل تلك الحالة، اضبط خيار pre_escape ليهرب بيانات الدخل قبل تشغيلها عبر المرشح الخاص بك:

$filter = new \Twig\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);

المرشحات المتغيرة Variadic Filters

إذا وجب على مرشح أن يقبل عددًا عشوائيًا من الوسائط، فاجعل خيار is_variadic على true، كي يمرِّر Twig الوسائط الإضافية كوسيط أخير في هيئة مصفوفة إلى استدعاء المرشح:

$filter = new \Twig\TwigFilter('thumbnail', function ($file, array $options = []) {
    // ...
}, ['is_variadic' => true]);

لكن انتبه إلى أن الوسائط المسمَّاة named arguments التي تمرَّر إلى مرشح متغير لا يمكن التحقق من صلاحيتها بما أنها ستؤول تلقائيًا إلى مصفوفة الخيارات option array.

المرشحات الديناميكية

المرشح الديناميكي هو مرشح يحتوي اسمه على المحرف الخاص *، وهذا المحرف مطابق لأي سلسلة نصية:

$filter = new \Twig\TwigFilter('*_path', function ($name, $arguments) {
    // ...
});

المرشحات التالية مطابقة للمرشح الديناميكي المعرَّف أعلاه:

  • product_path
  • category_path

يستطيع المرشح الديناميكي أن يعرِّف أكثر من جزء ليكون ديناميكيًا أيضًا:

$filter = new \Twig\TwigFilter('*_path_*', function ($name, $suffix, $arguments) {    // ...});

يستقبل المرشح كل قيم الجزء الديناميكي قبل وسائط المرشح العادي، لكن بعد البيئة والسياق، فمثلًا، إذا استدعينا ‎'foo'|a_path_b()‎ فستُمرَّر الوسائط ('a', 'b', 'foo') إلى المرشح.

المرشحات المهملة Deprecated Filters

تستطيع تحديد المرشح ليكون مهملًا من خلال ضبط الخيار deprecated على true، كما تستطيع تحديد مرشح بديل يكون بديلًا للمهمل إذا رأيت ذلك:

$filter = new \Twig\TwigFilter('obsolete', function () {
    // ...
}, ['deprecated' => true, 'alternative' => 'new_one']);

عند إهمال المرشح فإن Twig يرسل إشعار إهمال عند تصريف قالب يستخدم ذلك المرشح، انظر صفحة الوصفات للمزيد.

الدوال

تعرَّف الدوال بنفس طريقة تعريف المرشحات، لكن تحتاج إلى إنشاء نسخة من ‎\Twig\TwigFunction:

$twig = new \Twig\Environment($loader);
$function = new \Twig\TwigFunction('function_name', function () {
    // ...
});
$twig->addFunction($function);

تدعم الدوال نفس المزايا التي تدعمها المرشحات ما عدا خياري pre_escape و preserves_safety.

الاختبارات

تعَّرف الاختبارات بنفس طريقة تعريف الدوال والمرشحات، لكن تحتاج إلى إنشاء نسخة من ‎\Twig\TwigTest:

$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('test_name', function () {
    // ...
});
$twig->addTest($test);

تسمح لك الاختبارات بإنشاء منطق يختص بتطبيق ما من أجل تقييم الشرطيات البوليانية boolean conditions. مثلًا، لتنشئ اختبار Twig يتحقق إن كانت الكائنات حمراء red:

$twig = new \Twig\Environment($loader);
$test = new \Twig\TwigTest('red', function ($value) {
    if (isset($value->color) && $value->color == 'red') {
        return true;
    }
    if (isset($value->paint) && $value->paint == 'red') {
        return true;
    }
    return false;
});
$twig->addTest($test);

يجب أن تعيد دوال الاختبار إما true أو false. استخدم الخيار node_class عند إنشاء الاختبارات من أجل توفير تجميعة مخصصة للاختبار، وذلك ينفعك إن كان يمكن تصريف اختبارك داخل أنوع البيانات الأولية لـ PHP، وتستخدمه اختبارات عديدة مضمنة في Twig:

namespace App;

use Twig\Environment;
use Twig\Node\Expression\TestExpression;
use Twig\TwigTest;

$twig = new Environment($loader);
$test = new TwigTest(
    'odd',
    null,
    ['node_class' => OddTestExpression::class]);
$twig->addTest($test);

class OddTestExpression extends TestExpression
{
    public function compile(\Twig\Compiler $compiler)
    {
        $compiler
            ->raw('(')
            ->subcompile($this->getNode('node'))
            ->raw(' % 2 != 0')
            ->raw(')')
        ;
    }
}

يوضح المثال أعلاه كيف تنشئ اختبارات تستخدم صنف العقدة node class الذي يكون له وصول إلى عقدة فرعية اسمها node، تحتوي على القيمة التي تُختبر.

{% if my_value is odd %}

إذا استُخدم مرشح odd في شيفرة مثل التي أعلاه فإن عقدة node ستحتوي على تعبير لـ my_value، ويكون للاختبارات المبنية على عقد وصول أيضًا إلى عقدة arguments، وستحتوي تلك العقدة على وسائط أخرى عديدة تم توفيرها لاختبارك.

وإذا أردت تمرير عددًا متغيرًا من الوسائط الموضعية positional أو المسماة named إلى الاختبار فاضبط خيار is_variadic على true، إذ أن الاختبارات تدعم الأسماء الديناميكية انظر المرشحات الديناميكية للبنية اللغوية Syntax.

الوسوم

إحدى المزايا المثيرة في محرك قوالب مثل Twig هو إمكانية تعريف بنيات لغوية جديدة، وهي إحدى أعقد الإمكانيات التي تتعرض لها إذا أردت فهم كيفية عمل Twig، لكننا لن نحتاج إلى وسم في أغلب الأحيان:

  • إذا كان وسمك يولد بعض المخرجات فاستخدم دالة.
  • إذا كان وسمك يعدل بعض المحتوى ويعيده فاستخدم مرشحًا.

مثلًا، إذا أردت إنشاء وسم يحول نصًا بصيغة مارك داون إلى HTML، فأنشئ مرشح markdown بدلًا من إنشاء وسم:

{{ '**markdown** text'|markdown }}

إذا أردت استخدام هذا المرشح على نصوص كثيرة فغلفه بوسم apply:

{% apply markdown %}
Title
=====

Much better than creating a tag as you can **compose** filters.
{% endapply %}

إذا لم يكن وسمك يخرج أي شيء لكنه موجود بسبب أثر جانبي فأنشئ دالة لا تعيد شيئًا واستدعها من خلال وسم filter، فمثلًا إذا أردت إنشاء وسم يسجل النصوص فأنشئ دالة log واستدعها من خلال وسم do:

{% do log('Log some things') %}

والآن لننشئ وسمًا لبُنيةٍ لغوية جديدة وليكن set الذي يسمح بتعريف متغيرات بسيطة من داخل القالب، ويمكن استخدام ذلك الوسم كما يلي:

{% set name = "value" %}

{{ name }}

{# should output value #}

لاحظ أن وسم set جزء من توسيع النواة core وعليه سيكون متاحًا دائمًا، والنسخة المضمنة منه أقوى وتدعم إسنادات متعددة افتراضيًا. ستحتاج إلى ثلاثة خطوات من أجل تعريف وسم جديد:

  • تعريف صنف محلل للوحدات token parser class مسؤول عن تحليل شيفرة القالب.
  • تعريف صنف عقدة يكون مسؤولًا عن تحويل الشيفرة المحلَّلة إلى PHP.
  • تسجيل الوسم.

تسجيل وسم جديد

أضف وسمًا من خلال استدعاء التابع addTokenParser على نسخة ‎\Twig\Environment:

$twig = new \Twig\Environment($loader);
$twig->addTokenParser(new Project_Set_TokenParser());

تعريف محلل مفتاح Token Parser

لننظر الآن في الشيفرة الحقيقية لهذا الصنف:

class Project_Set_TokenParser extends \Twig\TokenParser\AbstractTokenParser
{
    public function parse(\Twig\Token $token)
    {
        $parser = $this->parser;
        $stream = $parser->getStream();

        $name = $stream->expect(\Twig\Token::NAME_TYPE)->getValue();
        $stream->expect(\Twig\Token::OPERATOR_TYPE, '=');
        $value = $parser->getExpressionParser()->parseExpression();
        $stream->expect(\Twig\Token::BLOCK_END_TYPE);

        return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
    }

    public function getTag()
    {
        return 'set';
    }
}

يجب أن يعيد التابع getTag()‎ الوسم الذي نريد تحليله، وهو set هنا، ويُستدعى التابع parse()‎ كلما وجد وسم set، ويجب أن يعيد نسخة من ‎\Twig\Node\Node تمثل العقدة (ستجد شرح إنشاء استدعاءات Project_Set_Node لاحقًا في الصفحة).

وتُبسَّط عملية التحليل بفضل مجموعة من التوابع التي يمكنك استدعاءها من مجرى مفتاح التشفير token stream أي ‎$this->parser->getStream()‎:

  • getCurrent()‎: تجلب المفتاح token الحالي في المجرى.
  • next()‎: تتحرك إلى المفتاح التالي في المجرى لكن تعيد المفتاح القديم.
  • test($type) أو test($value) أو test($type, $value): تحدد ما إذا كان المفتاح الحالي من نوع أو قيمة بعينها أو كليهما، وقد تكون القيمة مصفوفة من عدة قيم ممكنة.
  • expect($type[, $value[, $message]]): إذا لم يكن المفتاح الحالي من النوع المعطى أو القيمة المعطاة فيُرفع خطأ لغوي syntax error، وإلا إذا كان النوع صحيحًا وكذلك القيمة فيعاد المفتاح وينتقل المجرى إلى الوحدة التالية.
  • look()‎: ينظر إلى الوحدة التالية من غير استهلاكه.
  • يتم تحليل التعابير من خلال استدعاء parseExpression()‎ كما فعلنا في وسم set.

تستطيع تعلم جميع التفاصيل التي في عملية التحليل بقراءة أصناف TokenParser الموجودة فعليًا.

تعريف عقدة

إن الصنف Project_Set_Node نفسه قصير نوعًا ما:

class Project_Set_Node extends \Twig\Node\Node
{
    public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $line, $tag = null)
    {
        parent::__construct(['value' => $value], ['name' => $name], $line, $tag);
    }

    public function compile(\Twig\Compiler $compiler)
    {
        $compiler
            ->addDebugInfo($this)
            ->write('$context[\''.$this->getAttribute('name').'\'] = ')
            ->subcompile($this->getNode('value'))
            ->raw(";\n")
        ;
    }
}

ينفذ المصرِّف واجهة سلسلة ويوفر توابع تساعد المطور على توليد شيفرات PHP جميلة وسهلة القراءة:

  • subcompile()‎: يصرِّف العقدة.
  • raw()‎: يكتب السلسلة النصية كما هي.
  • write()‎: يكتب السلسلة النصية المعطاة بإضافة إزاحة في بداية كل سطر.
  • string()‎: يكتب سلسلة نصية بين علامتي اقتباس.
  • repr()‎: يكتب تمثيل PHP لقيمة معطاة، انظر ‎\Twig\Node\ForNode لمثال على استخدامه.
  • addDebugInfo()‎: يضيف السطر المتعلق بالعقدة الحالية من ملف القالب الأصلي كتعليق.
  • indent()‎: يزيح الشيفرة المولَّدة، انظر ‎\Twig\Node\BlockNode لمثال على الاستخدام.
  • outdent()‎: يزيح الشيفرة المولَّدة خارجيًا outdent ، انظر ‎\Twig\Node\BlockNode لمثال على الاستخدام.

إنشاء توسعة Extension

إن ما يجعلك تنشئ توسعة هو نقل الشيفرة المستخدمة بكثرة إلى صنف قابل لإعادة استخدامه مثل إضافة دعم للدُوَلية مثلًا internationalization، بأن يكون التطبيق مستخدمًا في عدة دول بعدة لغات مختلفة. ويستطيع التوسيع أن يعرِّف الوسوم والمرشحات والاختبارات والعوامل والدوال وزوار العقد node visitors .

ومن المفيد غالبًا أن تنشئ توسعة واحدة لمشروعك لاستضافة جميع الوسوم المحددة والمرشحات التي تريد إضافتها إلى Twig. كذلك، عند تحزيم شيفرتك في توسيع فإن Twig من الذكاء بحيث يعيد تصريف قوالبك كلما أجريت تغييرًا فيه، إذا كان auto_reload مفعلًا.

التوسعة Extension هي صنف يطبق الواجهة التالية:

interface \Twig\Extension\ExtensionInterface
{
    /**
     * يعيد نسخ محلل الرموز لإضافته إلى القائمة الحالية
     *
     * @return \Twig\TokenParser\TokenParserInterface[]
     */
    public function getTokenParsers();

    /**
     * يعيد نسخ زائد العقدة لإضافتها إلى القائمة الحالية
     *
     * @return \Twig\NodeVisitor\NodeVisitorInterface[]
     */
    public function getNodeVisitors();

    /**
     * يعيد قائمة من الفلاتر لإضافتها إلى القائمة الحالية
     *
     * @return \Twig\TwigFilter[]
     */
    public function getFilters();

    /**
     * يعيد قائمة من الاختبارات لإضافتها إلى القائمة الحالية
     *
     * @return \Twig\TwigTest[]
     */
    public function getTests();

    /**
     * يعيد قائمة من الدوال لإضافتها إلى القائمة الحالية
     *
     * @return \Twig\TwigFunction[]
     */
    public function getFunctions();

    /**
     * يعيد قائمة من العوامل لإضافتها إلى القائمة الحالية.
     * Returns a list of operators to add to the existing list.
     *
     * @return array<array> First array of unary operators, second array of binary operators
     */
    public function getOperators();
}

إذا أردت الحفاظ على صنف التوسيع الخاص بك رشيقًا فاجعله يرث من الصنف المضمن ‎\Twig\Extension\AbstractExtension بدلًا من تطبيق الواجهة بما أنه يوفر تطبيقات فارغة لجميع التوابع:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
}

لا تفعل هذه التوسعة أي شيء إلى الآن، وسنخصصها فيما يلي من الشرح، وتستطيع حفظها في أي مكان في نظام الملفات بما أن جميع التوسعات يجب أن تُسجل صراحة لتكون متاحة في قوالبك، كما تستطيع تسجيل توسيع باستخدام التابع addExtension()‎ على الكائن الرئيسي Enivronment الخاص بك:

$twig = new \Twig\Environment($loader);
$twig->addExtension(new Project_Twig_Extension());

تستطيع النظر في توسعات النواة core الخاصة بـ Twig إذ هي أمثلة ممتازة على كيفية عمل التوسعات.

المتغيرات العامة

يمكن تسجيل المتغيرات العامة في توسعة من خلال التابع getGlobals()‎:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
{
    public function getGlobals(): array
    {
        return [
            'text' => new Text(),
        ];
    }

    // ...
}

الدوال

يمكن تسجيل الدوال في توسعة من خلال التابع getFunctions()‎ :

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('lipsum', 'generate_lipsum'),
        ];
    }

    // ...
}

المرشحات

إذا أردت إضافة مرشح إلى توسعة فستحتاج إلى إعادة تعريف التابع getFilters()‎، فيجب أن يعيد هذا التابع مصفوفة من المرشحات لإضافتها إلى بيئة Twig:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFilters()
    {
        return [
            new \Twig\TwigFilter('rot13', 'str_rot13'),
        ];
    }

    // ...
}

الوسوم

يمكن إضافة الوسوم إلى توسعة بإعادة تعريف override التابع getTokenParsers()‎، فيجب أن يعيد مصفوفةً من الوسوم لإضافتها إلى بيئة Twig:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getTokenParsers()
    {
        return [new Project_Set_TokenParser()];
    }

    // ...
}

أضفنا في الشيفرة السابقة وسمًا واحدًا جديدًا، عرَّفه الصنف Project_Set_TokenParser، وهذا الصنف مسؤول عن تحليل الوسم وتصريفه إلى PHP.

العوامل Operators

يسمح لك التابع getOperators()‎ بإضافة عوامل جديدة، انظر الشيفرة التالية كمثال على كيفية إضافة العوامل ! و || و &&:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getOperators()
    {
        return [
            [
                '!' => ['precedence' => 50, 'class' => \Twig\Node\Expression\Unary\NotUnary::class],
            ],
            [
                '||' => ['precedence' => 10, 'class' => \Twig\Node\Expression\Binary\OrBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
                '&&' => ['precedence' => 15, 'class' => \Twig\Node\Expression\Binary\AndBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
            ],
        ];
    }

    // ...
}

الاختبارات

يسمح لك التابع getTests()‎ بإضافة دوال اختبار جديدة:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getTests()
    {
        return [
            new \Twig\TwigTest('even', 'twig_test_even'),
        ];
    }

    // ...
}

موازنة بين التعريف ووقت التشغيل

يمكن تعريف تنفيذات وقت التشغيل runtime implementations لمرشحات Twig ودواله واختباراته كأي نوع بيانات "callable" في PHP:

  • الدوال/التوابع الساكنة: بسيطة في تطبيقها، وسريعة -تستخدمها جميع توسعات core في Twig، لكن من الصعب أن يعتمد وقت التشغيل runtime على كائنات خارجية.
  • الإغلاقات closures : بسيطة في تنفيذها.
  • توابع الكائن: أكثر مرونة وتحتاجها إن كانت شيفرة وقت التشغيل لديك تعتمد على كائنات خارجية.

إن أبسط طريقة لاستخدام التوابع هي تعريفها على توسعة نفسها:

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    private $rot13Provider;

    public function __construct($rot13Provider)
    {
        $this->rot13Provider = $rot13Provider;
    }

    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('rot13', [$this, 'rot13']),
        ];
    }

    public function rot13($value)
    {
        return $this->rot13Provider->rot13($value);
    }
}

هذه الطريقة على بساطتها إلا أنه لا يُنصح بها لأنها تجعل تصريف القالب يعتمد على اعتماديات وقت التشغيل حتى لو لم تكن ثمة حاجة إليها. تستطيع فصل تعريفات توسعة من تنفيذات وقت تشغيلها بتسجيل نسخة من ‎\Twig\RuntimeLoader\RuntimeLoaderInterface على البيئة التي تعرف كيفية بدء أصناف وقت التشغيل تلك، ويجب أن تكون أصناف وقت التشغيل قابلة للتحميل التلقائي:

class RuntimeLoader implements \Twig\RuntimeLoader\RuntimeLoaderInterface
{
    public function load($class)
    {
        // $class طبق المنطق لإنشاء نسخة من 
        // وأدخل اعتمادياتها.
        // وسيعني ذلك في الغالب أن تستخدم حاوية حقن الاعتماديات
        // dependency injection container أو 
        if ('Project_Twig_RuntimeExtension' === $class) {
            return new $class(new Rot13Provider());
        } else {
            // ...
        }
    }
}

$twig->addRuntimeLoader(new RuntimeLoader());

لاحظ أن Twig يأتي بمحمِّل وقت تشغيل ‎\Twig\RuntimeLoader\ContainerRuntimeLoader متوافق مع PSR-11، وصار من الممكن أن تنقل منطق وقت التشغيل إلى صنف Project_Twig_RuntimeExtension جديد وتستخدمه مباشرة في التوسيع:

class Project_Twig_RuntimeExtension
{
    private $rot13Provider;

    public function __construct($rot13Provider)
    {
        $this->rot13Provider = $rot13Provider;
    }

    public function rot13($value)
    {
        return $this->rot13Provider->rot13($value);
    }
}

class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('rot13', ['Project_Twig_RuntimeExtension', 'rot13']),
            // أو
            new \Twig\TwigFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'),
        ];
    }
}

اختبار توسعة

الاختبارات الدالية

تستطيع إنشاء اختبارات دالّية Functional Tests للتوسعات من خلال إنشاء بنية الملف التالية في مجلد اختبارك:

Fixtures/
    filters/
        foo.test
        bar.test
    functions/
        foo.test
        bar.test
    tags/
        foo.test
        bar.test
IntegrationTest.php

ويجب أن يبدو ملف IntegrationTest.php كما يلي:

use Twig\Test\IntegrationTestCase;

class Project_Tests_IntegrationTest extends IntegrationTestCase
{
    public function getExtensions()
    {
        return [
            new Project_Twig_Extension1(),
            new Project_Twig_Extension2(),
        ];
    }

    public function getFixturesDir()
    {
        return __DIR__.'/Fixtures/';
    }
}

تستطيع العثور على أمثلة لواجهات الاختبار Fixtures داخل مستودع Twig، داخل مجلد tests/Twig/Fixtures.

اختبارات العقد

قد يكون اختبار زوار العقد معقدًا، لذلك اجلب حالات اختبارك عبر الوراثة من ‎\Twig\Test\NodeTestCase، وستجد أمثلة في مستودع Twig، داخل مجلد tests/Twig/Node.

انظر أيضًا

المصادر