الفرق بين المراجعتين ل"Twig/advanced"

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

مراجعة 14:12، 5 مايو 2021

توسيع 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.

انظر أيضًا

المصادر