توسيع Twig

من موسوعة حسوب
< Twig
مراجعة 17:12، 21 أبريل 2021 بواسطة أسامه-دمراني (نقاش | مساهمات) (إدخال 2.0 تمام المحتوى وانظر أيضًا والتصانيف والمصادر.)
اذهب إلى التنقل اذهب إلى البحث

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

وانتبه إلى أن توسيع Twig من غير إنشاء توسيع جديد يجعل Twig غير قادر على إعادة تصريف (recompile) قوالبك عند تحديث شيفرة PHP، ويجب أن تعطل تخزين القوالب المؤقت (template caching) أو تحزِّم شيفرتك في توسيع كي ترى تغييراتك في الوقت الحقيقي، كما سيلي بيانه لاحقًا في هذه الصفحة. كذلك يجب أن تفهم أولًا الاختلافات بين نقاط التوسيع المختلفة ومتى يجب استخدام كل منها، قبل توسيع 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):

1
2
3
4
5
6
7
8
9
10
11
$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:

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;

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());

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

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

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

تعريف العقدة

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

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
{
    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 لمثال على الاستخدام.

إنشاء توسيع

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

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

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

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
{
    /**
     * يعيد نُسخ محلل الرموز لإضافته إلى القائمة الحالية.
     *
     * @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()‎ :

1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
{
    public function getGlobals(): array
    {
        return [
            'text' => new Text(),
        ];
    }

    // ...
}

الدوال

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

1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getFunctions()
    {
        return [
            new \Twig\TwigFunction('lipsum', 'generate_lipsum'),
        ];
    }

    // ...
}

الفلاتر

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

1
2
3
4
5
6
7
8
9
10
11
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()‎ بإضافة عوامل جديدة، انظر الشيفرة التالية كمثال على كيفية إضافة العوامل ! و || و &&:

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
{
    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()‎ بإضافة دوال اختبار جديدة:

1
2
3
4
5
6
7
8
9
10
11
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
{
    public function getTests()
    {
        return [
            new \Twig\TwigTest('even', 'twig_test_even'),
        ];
    }

    // ...
}

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

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

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

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

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
{
    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 على البيئة التي تعرف كيفية بدأ أصناف وقت التشغيل تلك، ويجب أن تكون أصناف وقت التشغيل قابلة للتحميل التلقائي:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 جديد وتستخدمه مباشرة في التوسيع:

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
{
    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)

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

1
2
3
4
5
6
7
8
9
10
11
Fixtures/
    filters/
        foo.test
        bar.test
    functions/
        foo.test
        bar.test
    tags/
        foo.test
        bar.test
IntegrationTest.php

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.

انظر أيضًا

المصادر