4 روش برای افزایش سرعت توسعه سیمفونی با PackageBuilder

همانطور که از عنوان مقاله مشخص است، موضوع بحث این مقاله افزایش سرعت فریمورک Symfony است. قبل از اینکه به جزئیات بپردازیم، توضیح کوتاهی درباره Symfony داشته‌باشیم.

Symfony  چیست و چرا؟

Symfony یک فریم‌ورک است که به زبان php نوشته‌شده‌است و هدف اول و آخر آن، سرعت است. سیمفونی با هدف افزایش سرعت و کاهش و جایگزینی تسک‌های تکراری برنامه نویسی منتشر شد.

Simplify 4 منتشر شد و همراه آن یک پکیج وجود‌ دارد که شامل تمام تغییرات Symfony است که بسته‌های Simplify از آن استفاده می‌کنند.
رندر قابل پرتاب (Throwable)؟ سرویس‌های تستی بدون public violation؟ پارامترها با glob بارگیری می‌شوند؟ در این مقاله این موارد را تحت پوشش قرار دادیم!

 

در این مقاله 4 روش وجود دارد که به Symplify 4 اضافه شده‌اند و بلافاصله در برنامه قابل استفاده است. تنها کافیست آن را نصب کنید.

composer require symplify/package-builder

... و از این 4 ویژگی جدید لذت ببرید:

1. Console-Like -vvv-Aware نمایش‌دهنده استثنا و ارورها

اگر از کنسول سیمفونی استفاده می‌کنید، احتمالاً با این خطاها آشنا هستید و با -vvv ردیابی استثنای کامل را بدست می‌آورید: خطاها بدون و با vvv

همچنین با Error مانند ParseError کار می‌کند. این قابلیت فوق‌العاده سریع، مفید و جهانی است.

اما اگر شما نیاز به استفاده از گزارش خطای مستقل داشته‌باشید، چه می‌کنید. مثلا قبل از ساخت کنسول؟

$containerFactory = new ContainerFactory();
$container = $containerFactory->createFromConfig('config-not-found.yml');

$application = $container->get(Application::class);
$application->run();

می‌توانید از SymfonyStyle استفاده کنید:

use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

try {
    $containerFactory = new ContainerFactory();
    $containerFactory->createFromConfig('config-with-parse-error.yml');

    $application = $container->get(Application::class);
    $application->run();
} catch (Throwable $throwable) {
    (new SymfonyStyle(new ArgvInput(), new ConsoleOutput()))->error($throwable);
}

که متاسفانه گزارش نسبتا نامنظمی به شما می‌دهد.

[ERROR] Symfony\Component\Yaml\Exception\ParseException: Unable to parse at line 9 (near "@# global templates
         variables"). in /var/www/tomasvotruba.com/vendor/symfony/yaml/Parser.php:415
         Stack trace:
         #0 /var/www/tomasvotruba.com/vendor/symfony/yaml/Parser.php(454): Symfony\Component\Yaml\Parser->doParse(' @#
         global temp...', 768)
         #1 /var/www/tomasvotruba.com/vendor/symfony/yaml/Parser.php(315): Symfony\Component\Yaml\Parser->parseBlock(8,
         '@# global templ...', 768)
         #2 /var/www/tomasvotruba.com/vendor/symfony/yaml/Parser.php(95): Symfony\Component\Yaml\Parser->doParse(Array,
         768)
         #3 /var/www/tomasvotruba.com/vendor/symfony/yaml/Parser.php(62):
         Symfony\Component\Yaml\Parser->parse('imports:\n    - ...', 768)
         #4 /var/www/tomasvotruba.com/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php(621):
         Symfony\Component\Yaml\Parser->parseFile('/var/www/tomasv...', 768)
         #5
         /var/www/tomasvotruba.com/vendor/symplify/package-builder/src/Yaml/AbstractParameterMergingYamlFileLoader.php(52
         ): Symfony\Component\DependencyInjection\Loader\YamlFileLoader->loadFile('/var/www/tomasv...')
         #6 /var/www/tomasvotruba.com/vendor/symfony/config/Loader/DelegatingLoader.php(40):
         Symplify\PackageBuilder\Yaml\AbstractParameterMergingYamlFileLoader->load('/var/www/tomasv...', NULL)
         #7 /var/www/tomasvotruba.com/vendor/symplify/statie/src/DependencyInjection/StatieKernel.php(43):
         Symfony\Component\Config\Loader\DelegatingLoader->load('/var/www/tomasv...')
         #8 /var/www/tomasvotruba.com/vendor/symfony/http-kernel/Kernel.php(614):
        ...

 

چگونه می‌توان گزارش خطای مناسبی را حتی خارج از محدوده برنامه کنسول دریافت کرد؟

 

آیا برای کارکردن با برنامه CLI خود به این مورد نیاز دارید؟ منطق برنامه Symfony\Console مجزا ارائه شد.

نام آن Symplify\PackageBuilder\Console\ThrowableRenderer است و از آن به این شکل استفاده می‌شود:

use Symplify\PackageBuilder\Console\ThrowableRenderer;

try {
    $containerFactory = new ContainerFactory();
    $containerFactory->createFromConfig('config-not-found.yml');

    $application = $container->get(Application::class);
    $application->run();
} catch (Throwable $throwable) {
    (new ThrowableRenderer())->render($throwable);
}

و همیشه گزارش خطای مناسبی برای هر Throwable خواهید داشت. بلافاصله در هر جایی کار کنید و همچنین آپشن‌ -vvv را در نظر داشته باشید.

2. برای هر سرویسی که تست می‌کنید، درست است public: true دستی را کنار بگذارید

اگر شما نیاز به تست یک سرویس دارید، متداول‌ترین روش برای آزمایش آن با استفاده از DI در ادامه آمده‌است:

final class ChangelogLinkerTest extends AbstractContainerAwareTestCase
{
    protected function setUp(): void
    {
        $this->changelogLinker = $this->container->get(ChangelogLinker::class);
    }

    // ...
}

اما اگر آن را اینگونه فراخوانی کنید، لازم است بدانید که باید public یا عمومی باشد.
برای تحقق این امر، دولوپرها یکی از 2 مسیر را طی می‌کنند. هر دو با قابلیت نگهداری بالا:

  1. 1.عمومی یا public برای تمامی کلاس تست‌شده

  2. services:
        SomeNamespace\:
            resource: '..'
    
        SomeNamespace\SomeClass:
            public: true

2. کانفیگ Tests-only سفارشی

# services-tests.yml
services:
    SomeNamespace\SomeClass:
        public: true

هر دوی این کانفیگ‌ها به آپدیت‌های دستی شما بستگی دارند. این راهی نیست که ما به دنبال آن هستیم، برنامه نویسی باید آسان، سرگرم‌کننده و بدون هیچ گونه اعصاب خوردی و درگیری ذهنی باشد.

چگونه می توان بر این موضوع غلبه کرد؟

فقط با اضافه‌کردن Symplify\PackageBuilder\DependencyInjection\CompilerPass\PublicForTestsCompilerPass

final class AppKernel extends Kernel
{
    // ...

    protected function build(ContainerBuilder $containerBuilder): void
    {
        $containerBuilder->addCompilerPass(new PublicForTestsCompilerPass());
    }
}

این برنامه PHPUnit را شناسایی کرده و public را به هر سرویسی اضافه می‌کند، بنابراین نیازی نیست که آن را برای هر سرویس جدیدی که به مجموعه خود اضافه کرده‌اید، اضافه کنید.
به نوعی راه اندازی و فراموش کردن است.

3. اینترفیس‌های Single-Implemented لایووایر

از زمان ارائه Symfony 3.4، خودکارکردن در ترکیب با بارگیری خودکار PSR-4 بسیار مفید است. اما در مورد سرویس‌های سوم شخص یا به اصطلاح third-party که دارای اینترفیس هستند، چطور؟

# app/config/services.yml
services:
    _defaults:
        autowire: true

    Symfony\Component\Console\Input\ArgvInput: ~
    Symfony\Component\Console\Output\ConsoleOutput: ~

 

اگر از Symfony \ Component \ Console \ Input \ InputInterace استفاده می‌کنید، خطای عدم اجرای آن را دریافت می‌کنید.

برای حل آن باید از نام مستعار برای هر کلاس که یک رابط را پیاده‌سازی می‌کند، استفاده کنید:

# app/config/services.yml
services:
    _defaults:
        autowire: true

    Symfony\Component\Console\Input\ArgvInput: ~
    Symfony\Component\Console\Input\InputInterace:
        alias: Symfony\Component\Console\Input\ArgvInput

    Symfony\Component\Console\Output\ConsoleOutput: ~
    Symfony\Component\Console\Output\OutputInterace:
        alias: Symfony\Component\Console\Output\ConsoleOutput

به این ترتیب، به دلیل استفاده از کد تمیز و جداسازی رابط‌ها در کد خود مجازات می‌شوید، زیرا استفاده از Symfony\Component\Console\Input\ArgvInput آسان‌تر خواهد ‌بود. اما آیا شکستن اصول SOLID فقط برای رعایت رفتارهای Symfony ضروری است؟ فکر نمی کنم که این فریم‌ورک طراحی بدی در برنامه شما اعمال کند.

چگونه آن را برطرف کنیم؟

در کد زیر از پول ریکوئست 25282 سیفونی الهام گرفته شده است که در هنگام دیسکاوری کردن PSR-4 اینترفیس‌های singly-implemented را رجیستر می‌کند.

namespace App;

use Symfony\Component\HttpKernel\Kernel;
use Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutowireSinglyImplementedCompilerPass;

final class AppKernel extends Kernel
{
    // ...
    protected function build(ContainerBuilder $containerBuilder): void
    {
        $containerBuilder->addCompilerPass(new AutowireSinglyImplementedCompilerPass());
    }
}

سپس تنظیمات خود را به همان روشی که PSR-4 autodiscovery انجام می‌شود، تمیز کنید:

# app/config/services.yml
 services:
     _defaults:
         autowire: true

     Symfony\Component\Console\Input\ArgvInput: ~
-    Symfony\Component\Console\Input\InputInterace:
-        alias: Symfony\Component\Console\Input\ArgvInput

     Symfony\Component\Console\Output\ConsoleOutput: ~
-    Symfony\Component\Console\Output\OutputInterace:
-        alias: Symfony\Component\Console\Output\ConsoleOutput

4. چگونه پارامترهای چندین فایل را به روش ایمن جدا کنیم؟

ترجیح نمی‌دهید لیست پارامترهای طولانی را به چندین فایل و بعد آن‌ها با Glob جدا کنید؟

# app/config/config.yml
imports:
    - { resource: 'framework/*.yml' }

در دایرکتوری /framework دو فایل وجود دارد:

# app/config/framework/symfony.yml
parameters:
    framework:
        symfony:
            controller: '<?php "some Symfony code"'

و

# app/config/framework/laravel.yml
parameters:
    framework:
        laravel:
            controller: '<?php "some Laravel code"'

پارامتر framework چند آیتم دارد؟ 0؟ 1؟ 2؟

یکی از این موارد درست است اما کدام؟ laravel یا symfony ؟ طبقYamlFileLoader، از رویکرد آخرین برنده‌ها استفاده‌ شده ‌است. احتمالاً symfony ... اما مهم نیست، چون شما به همه آن‌ها احتیاج دارید.

چگونه ادغام پارامترها را برتری دهیم؟

موضوع اصلی ایجاد اکستنشن،پیکربندی ،باندل و مرج کلاس است. سپس یک پیاده‌سازی سفارشی از اتصال پارامتر (parameter binding) و سایر ویژگی‌های مربوط به پارامترهای Symfony مانند ترکیب پارامترها، متغیرهای env و غیره اضافه می‌شود.

Simplify از روش پیشنهادی پیروی می‌کند و کدهای کپی شده بسیاری از Symfony\Dependency Injection است که به سختی کار می‌کنند.

برای ذخیره بسیاری از کلاس‌های تکراری و استفاده از تمام ویژگی‌های پارامتر Symfony، می‌توانید YamlFileLoader را بیش از حد بارگذاری کنید، جایی که پارامترها با هم ادغام می‌شوند:

# app/config/framework/symfony.yml
parameters:
    framework:
        symfony:
            controller: '<?php "some Symfony code"'
        laravel:
            controller: '<?php "some Laravel code"'

اگر به این نیاز دارید؟ کافیست از Symplify\PackageBuilder\Yaml\AbstractParameterMergingYamlFileLoader در کلاس Kernel خود استفاده کنید:

use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\GlobFileLoader;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Kernel;

final class AppKernel extends Kernel
{
    // ...

    /**
     * @param ContainerInterface|ContainerBuilder $container
     */
    protected function getContainerLoader(ContainerInterface $container): DelegatingLoader
    {
        $kernelFileLocator = new FileLocator($this);

        $loaderResolver = new LoaderResolver([
            new GlobFileLoader($container, $kernelFileLocator),
            new class($container, $kernelFileLocator) extends AbstractParameterMergingYamlFileLoader {
            },
        ]);

        return new DelegatingLoader($loaderResolver);
    }
}

کلاس abstract است، بنابراین می‌توانید آن را به هر روشی که نیاز دارید اصلاح کنید.
ساخت پکیج مبارک!

رقیه اباذری

رقیه اباذری

تو دانشگاه IT خوندم و اکثر منابع کتاب‌های ترجمه شده بودند و صدالبته مبهم :( مثلا element رو "عنصر" ترجمه می‌کردن و من همیشه می‌رفتم تو شیمی و جدول مندلیف. تو باورژن سعی کردم تا حد ممکن مطالب رو با زبان ساده و قابل درک بنویسم. باشد که کسانی که تازه پا به عرصه برنامه‌نویسی گذاشتن، راغب‌تر بشن و با نظرات و فیدبک‌های شما راه هموارتر بشه:)

دیدگاه‌ها


ثبت دیدگاه