Пояснення змін контейнера DI в Symfony 3.3 (autowiring, _defaults та ін.)
Дата оновленн перекладу 2023-06-05
Пояснення змін контейнера DI в Symfony 3.3 (autowiring, _defaults та ін.)
Якщо ви подивитеся на файл services.yml
у новому проекті Symfony 3.3, то ви помітите
деякі великі зміни: _defaults
, autowiring
, autoconfigure
та інші.
Ці функції створені для автоматизації конфігурації та для того, щоб розробка стала
швидшою, не жертвуючи передбачуваністю, яка дуже важлива! Ще одна мета - змусити
контролери та сервіси поводитися більш послідовно. У Symfony 3.3, контролери є сервісами
за замовчуванням.
Документація вже була оновлена так, щоб припустити, що у вас увімкнено ці нові функції. Якщо ви вже існуючий користувач Symfony і хочете зрозуміти "що" і "чому", які стоять за цими змінами, то ця стаття для вас!
Всі зміни необовʼязкові
Найважливіше - сьогодні ви можете оновитися до Symfony 3.3, не вносячи жодних змін у ваш додаток. Symfony має сувору обіцянку зворотної сумісності, що означає, що оновлюватися до спрощених версій завжди безпечно.
Усі нові функції є необов'язковими: вони не вмикаються за замовчуванням, тож що вам потрібно змінити ваші файли конфігурації, щоб їх використовувати.
Новий файл services.yml за замовчуванням
Щоб зрозуміти зміни, подивіться на новий файл services.yml
за замовчуванням,
у стандартній версії Symfony:
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
# app/config/services.yml
services:
# конфігурація за замовчуванням для сервісів у *цьому* файлі
_defaults:
# автоматично впроваджує залежності у ваших сервісах
autowire: true
# автоматично реєструє ваші сервіси як команди, підписники подій і т.д.
autoconfigure: true
# це означає, що ви не можете викликати сервіси напряму з контейнера через $container->get()
# якщо вам потрібно зробити це, ви можете перевизначити це налаштування в окремих сервісах
public: false
# робить так, щоб класи в src/AppBundle були доступні як сервіси
# це створює по сервісу на клас, id яких є повністю кваліфікованим іменем класу
AppBundle\:
resource: '../../src/AppBundle/*'
# ви можете виключити каталоги або файли,
# але якщо сервіс не використовується, він все одно видаляється
exclude: '../../src/AppBundle/{Entity,Repository}'
# контролери імпортуються окремо, щоб переконатися в тому, що вони публічні
# та мають тег, який дозволяє дії з типізації сервісів
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
# додайте більше сервісів або перевизначіть сервіси, які вимагають монтажу вручну
# AppBundle\Service\ExampleService:
# аргументи:
# $someArgument: 'some_value'
Ця маленька частина конфігурації містить зміну понять того, як конфігуруються сервіси в Symfony.
1) Сервіси завантажуються автоматично
See also
Прочитайте документацію з автоматичного завантаження сервісів .
Перша найбільша зміна в тому, що сервіси більше не повинні бути визначені один за одним, завдяки наступній конфігурації:
1 2 3 4 5 6 7 8 9 10 11
# app/config/services.yml
services:
# ...
# робить класи в src/AppBundle доступними для використання в якості сервісів
# це створює по сервісу на клас, id яких є повністю кваліфікованим іменем класу
AppBundle\:
resource: '../../src/AppBundle/*'
# ви можете виключити каталоги або файли,
# але якщо сервіс не використовується, він все одно видаляється
exclude: '../../src/AppBundle/{Entity,Repository}'
Це означає, що кожен клас в src/AppBundle/
доступний для використання в
якості сервісу. І завдяки частині _defaults
зверху файлу, всі ці сервіси є
автомонтованими і приватними (тобто public: false
).
Id сервісів дорівнюють імені класу (наприклад, AppBundle\Service\InvoiceGenerator
).
І це ще одна зміна, яку ви помітите в Symfony 3.3: ми рекомендуємо вам використовувати
ім'я класу як id вашого сервісу, хіба що у вас не
багато сервісів для одного класу .
Але як контейнер може знати аргументи мого сервісу?
Оскільки кожен сервіс є автомонтованим , контейнер може визначити більшість аргументів автоматично. Але ви завжди можете перевизначити сервіс і сконфігурувати аргументи вручну або зробити щось інше особливе з вашим сервісом.
Але зачекайте, якщо у мене є якісь класи моделі (не сервіси) в моєму каталозі
src/AppBundle/
, хіба це не означає, що вони теж будуть зареєстровані, як сервіси? Хіба це не проблема?
Насправді, це не проблема. Оскільки всі нові сервіси `), тобто якісь із сервісів не використовуються у вашому коді, вони будуть автоматично видалені зі скомпільованого контейнера. Це означає, що кількість сервісів у вашому контейнері має бути однаковою незалежно від того, чи ясно чи ви конфігуруєте кожен сервіс, чи завантажите їх усі одномоментно за допомогою цього методу.
Гаразд, але чи можу я виключити деякі шляхи, які я знаю, що не містять сервісів?
Так! Ключ exclude
- це глобальний шаблон, який можна використовувати для внесення
до чорного списку тих шляхів, які ви не хочете включати, як сервіси. Але, оскільки
невикористовувані сервіси автоматично видаляються з контейнера, exclude
не такий вже й
важливий. Найбільшою перевагою є те, що ці шляхи не відстежуються контейнером,
і тому можуть призвести до того, що контейнеру потрібно буде рідше перебудовуватися в оточенні
dev
.
2) Автомонтаж за замовчуванням: використання типізованого замість id сервісу
Друга велика зміна - автомонтування ввімкнено (через _defaults
) для всіх
зареєстрованих вами сервісів. Це також означає, що id сервісів тепер менш
важливі, а "типи" (тобто імена класу або інтерфейсу) тепер більш важливі.
Наприклад, до Symfony 3.3 (і це все ще дозволяється), ви могли передати один сервіс як аргумент до іншого з такою конфігурацією:
1 2 3 4 5 6 7 8 9
# app/config/services.yml
services:
app.invoice_generator:
class: AppBundle\Service\InvoiceGenerator
app.invoice_mailer:
class: AppBundle\Service\InvoiceMailer
arguments:
- '@app.invoice_generator'
Щоб передати InvoiceGenerator
як аргумент до InvoiceMailer
, вам
потрібно було вказати id сервісу, як аргумент: app.invoice_generator
. Id сервісів
були головним способом конфігурації речей.
Але в Symfony 3.3, завдяки автомонтуванню, все, що вам треба зробити - це типізувати
аргумент за допомогою InvoiceGenerator
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// src/AppBundle/Service/InvoiceMailer.php
// ...
class InvoiceMailer
{
private $generator;
public function __construct(InvoiceGenerator $generator)
{
$this->generator = $generator
}
// ...
}
Ось і все! Обидва сервіси автоматично зареєстровані
і встановлені для автомонтування. Без будь-якої конфігурації, контейнер знає, що
треба передати автоматично зареєстрований AppBundle\Service\InvoiceGenerator
в
якості першого аргументу. Як ви бачите, тип класу - AppBundle\Service\InvoiceGenerator
- це найважливіше, не id. Ви запитуєте екземпляр конкретного типу і контейнер автоматично
передає вам правильний сервіс.
Ну хіба не магія? Як він знає, який саме сервіс передати мені? Що, якщо у мене безліч сервісів одного екземпляра?
Система автомонтування була створена так, щоб бути дуже передбачуваною. Спочатку вона працює шляхом пошуку сервісу, іd якого точно збігається з типізацією. Це означає, що ви повністю контролюєте, яке типізування веде до якого сервісу. Ви навіть можете використовувати псевдоніми сервісів, щоб отримати більше контролю. Якщо у вас безліч сервісів конкретного типу, ви вибираєте, які з них мають бути використані для автомонтування. Щоб дізнатися всі деталі про логіку автомонтування, див. .
Але що, якщо в мене скалярний (наприклад, рядок) аргумент? Як він автомонтується?
Якщо у вас є аргумент, який не є об'єктом, то він не може бути автоматично змонтований. Але це не страшно! Symfony надасть вам чіткий виняток (при подальшому оновленні будь-якої сторінки), що повідомляє вам, який аргумент якого сервісу не міг бути автозмонтований. Щоб виправити це, ви можете вручну сконфігурувати *тільки* цей один аргумент . У цьому полягає філософія автомонтування: конфігурувати тільки ті частини, які вам необхідні. Більшість конфігурацій автоматизовані.
Гаразд, але автомонтування робить ваш додаток менш стабільним. Якщо ви зміните одну річ або зробите помилку, можуть трапитися непередбачувані речі. Хіба це не проблема?
Symfony завжди цінувала стабільність, безпеку та передбачуваність в першу чергу. Автомонтування було створено з думкою про це. Зокрема:
- Якщо є проблема з монтуванням будь-якого аргументу до будь-якого сервісу, видається чітке виключення при подальшому оновленні будь-якої сторінки, навіть якщо ви не використовуєте цей сервіс на цій сторінці. Це потужно: неможливо зробити помилку автомонтування і не помітити цього.
- Контейнер визначає, який сервіс передати в детальній манері: він шукає сервіс, id якого точно збігається з типізуванням. Він не сканує всі сервіси, у пошуках об'єктів, які мають цей клас або інтерфейс (насправді, він робить це в Symfony 3.3, але це застаріло. Якщо ви покладаєтеся на це, то ви побачите чітке попередження про старіння).
Автомонтування прагне автоматизувати конфігурацію без магії.
3) Контролери реєструються як сервіси
Третя велика зміна полягає в тому, що в новому проекті Symfony 3.3, ваші контролери - це сервіси:
1 2 3 4 5 6 7 8 9 10
# app/config/services.yml
services:
# ...
# контролери імпортуються окремо, щоб переконатися в тому, що вони публічні
# і мають тег, який дозволяє дії з типізування сервісів
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
Однак, ви цього можете не помітити. По-перше, ваші контролери все ще можуть
розширювати той самий базовий клас Controller
або новий.
Це означає, що у вас є доступ до всіх тих самих скорочень, що й раніше. Крім
того, анотація @Route
і синтаксис _controller
(наприклад, AppBundle:Default:homepage
),
використовувані в маршрутизації, автоматично використовуватимуть ваш контролер в
якості сервісу (доти, доки id сервісу збігатиметься з ім'ям класу, що правильно в цьому
випадку). Дивіться Як визначати контролери як сервіси, щоб дізнатися більше деталей. Ви навіть можете
створити викликані контролери .
Іншими словами, все працює точно так само. Ви навіть можете додавати конфігурацію, описану вище, до вашого наявного проекту без будь-яких проблем: ваші контролери поводитимуться так само, як і раніше. Але тепер, коли ваші контролери є сервісами, ви можете використовувати впровадження залежності та автомонтування, як будь-який інший сервіс.
Щоб ще більше полегшити вам життя, тепер можливо автомонтувати аргументи у ваших методах дій контролера, так само, як ви можете це зробити з конструктором сервісів. Наприклад:
1 2 3 4 5 6 7 8 9
use Psr\Log\LoggerInterface;
class InvoiceController extends Controller
{
public function listAction(LoggerInterface $logger)
{
$logger->info('A new way to access services!');
}
}
Це можливо тільки у контролері, і ваш сервіс контролера повинен бути тегований
за допомогою controller.service_arguments
, щоб це спрацювало. Ця нова функція
використовується по всій документації.
Загалом, новою найкращою практикою є використання нормального конструктора
впровадження залежності (або впровадження "дії" в контролерах), замість виклику публічних
сервісів через $this->get()
(хоча це все ще працює).
4) Автотегування з автоконфігурацією
Остання велика зміна - це ключ autoconfigure
, який встановлено як
true
в _defaults
. Завдяки цьому, контейнер буде автоматично тегувати
сервіси, зареєстровані в цьому файлі. Наприклад, уявіть, що ви хочете створити
підписника подій. Для початку, ви створюєте клас:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// src/AppBundle/EventSubscriber/SetHeaderSusbcriber.php
// ...
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SetHeaderSusbcriber implements EventSubscriberInterface
{
public function onKernelResponse(FilterResponseEvent $event)
{
$event->getResponse()->headers->set('X-SYMFONY-3.3', 'Less config');
}
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => 'onKernelResponse'
];
}
}
Чудово! У Symfony 3.2 або більш ранніх версіях, вам би зараз знадобилося реєструвати
це як сервіс у services.yml
і тегувати за допомогою kernel.event_subscriber
. В
Symfony 3.3, ви вже закінчили! Сервіс зареєстрований автоматично .
І завдяки autoconfigure
, Symfony автоматично тегує сервіс, оскільки він реалізує
EventSubscriberInterface
.
Це звучить як магія - він автоматично тегує мої сервіси?
У цьому випадку, ви створили клас, який реалізує EventSubscriberInterface
і
зареєстрували його як сервіс. Це більш, ніж достатньо для того, щоб
контейнер знав, що ви хочете використовувати це як передплатника подій:
подальша конфігурація не потрібна. А система тегування - це власний
механізм Symfony. І звичайно ж, ви завжди можете встановити autowire
за замовчуванням
у значенні "false" в services.yml
, або відключити його для конкретного сервісу.
Це означає, що теги померли? Це працює для всіх тегів?
Це не працює для всіх тегів. Багато тегів мають обов'язкові атрибути, як слухачі подій, коли вам також потрібно вказати ім'я події та метод у вашому тезі. Автоконфігурація працює тільки для тегів, які не мають обов'язкових атрибутів тегу, і коли ви прочитаєте документи щодо функції, з них ви дізнаєтеся, чи потрібен тег. Ви також можете подивитися класи розширень (наприклад, FrameworkExtension для 3.3.0), щоб побачити, що вони конфігурують автоматично.
Що якщо мені треба додати до мого тегу пріоритетність?
Безліч автоматично сконфігурованих тегів мають необов'язкову пріоритетність. Якщо вам потрібно вказати пріоритет (або будь-який інший необов'язковий атрибут тегу), то це не проблема! Просто сконфігуруйте ваш сервіс вручну і додайте тег. Ваш тег буде перевершувати за значимістю той, що був доданий автоконфігурацією.
Що стосовно продуктивності
Symfony унікальна, тому що вона має скомпільований контейнер. Це означає, що немає жодного впливу на час прогону продуктивності під час використання будь-яких із цих функцій. Це також те, чому система автомонтування може видавати вам такі чіткі помилки.
Однак, є деякий вплив на продуктивність в оточенні dev
. Найважливіше -
ваш контейнер, найімовірніше, буде перестаріваться частіше при зміні ваших класів
сервісу. Це так тому що йому потрібно перебудовуватися щоразу, коли ви додаєте
до сервісу новий аргумент, або додаєте інтерфейс до вашого класу, який має бути сконфігурований
автоматично.
У дуже великих проєктах це може стати проблемою. Якщо так трапиться, ви завжди можете вибрати не використовувати автомонтування. Якщо ви думаєте, що система перебудови кешу могла б бути розумнішою в якійсь ситуації, будь ласка, створіть питання!
Оновлення до нової конфігураці Symfony 3.3
Готові оновити ваш існуючий проект? Чудово! Уявіть, що у вас є наступна конфігурація:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# app/config/services.yml
services:
app.github_notifier:
class: AppBundle\Service\GitHubNotifier
arguments:
- '@app.api_client_github'
markdown_transformer:
class: AppBundle\Service\MarkdownTransformer
app.api_client_github:
class: AppBundle\Service\ApiClient
arguments:
- 'https://api.github.com'
app.api_client_sl_connect:
class: AppBundle\Service\ApiClient
arguments:
- 'https://connect.sensiolabs.com/api'
Це необовʼязково, але давайте оновимо це до конфігурації Symfony 3.3 крок за кроком, не зламавши наш додаток.
Крок 1): Додавання _defaults
Почніть з додавання розділу _defaults
з autowire
та autoconfigure
.
1 2 3 4 5 6 7
# app/config/services.yml
services:
+ _defaults:
+ autowire: true
+ autoconfigure: true
# ...
Цей крок дуже простий: ви вже ясно конфігуруєте всі ваші сервіси. Так що
autowire
не робить нічого. Ви також вже тегуєте всі ваші сервіси, так що
autoconfigure
теж не змінює жодні наявні сервіси.
Ви поки ще не додали public: false
. Це станеться за хвилину.
Крок 2) Використання id сервісів класу
Наразі id сервісів - це машинні імена, наприклад, app.github_notifier
.
Щоб добре працювати з новою системою конфігурації, ваші id сервісів повинні бути іменами
класу, крім тих випадків, коли у вас безліч екземплярів одного і того ж сервісу.
Почніть з оновлення id сервісів до імен класу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# app/config/services.yml
services:
# ...
- app.github_notifier:
- class: AppBundle\Service\GitHubNotifier
+ AppBundle\Service\GitHubNotifier:
arguments:
- '@app.api_client_github'
- markdown_transformer:
- class: AppBundle\Service\MarkdownTransformer
+ AppBundle\Service\MarkdownTransformer: ~
# оставьте id, так как существует множество экземпляров в классе
app.api_client_github:
# ...
app.api_client_sl_connect:
# ...
Але ця зміна зламає наш додаток! Старі id сервісів (наприклад, app.github_notifier
)
більше не існують. Найпростіший спосіб виправити це - знайти всі ваші старі id сервісів,
і оновити їх до нових id класу: app.github_notifier
до AppBundle\Service\GitHubNotifier
.
У великих проектах є кращий спосіб: створіть союзницькі успадкування, які зв'яжуть старий
id з новим. Створіть файл legacy_aliases.yml
:
1 2 3 4 5 6 7
# app/config/legacy_aliases.yml
services:
# псевдоніми для того, щоб до старих id сервісів залишався доступ
# видаліть їх якщо/коли ви не викликаєте їх напряму
# з контейнера через $container->get()
app.github_notifier: '@AppBundle\Service\GitHubNotifier'
markdown_transformer: '@AppBundle\Service\MarkdownTransformer'
Потім, імпортуйте це зверху services.yml
:
1 2 3 4 5
# app/config/services.yml
+ imports:
+ - { resource: legacy_aliases.yml }
# ...
Ось і все! Старі id сервісів все ще працюють. Пізніше, (див. крок з очищення нижче), ви можете видалити їх зі свого додатку.
Крок 3) Зробіть сервіси приватними
Тепер ви готові зробити так, щоб всі сервіси за замовчуванням були приватними:
1 2 3 4 5 6 7 8
# app/config/services.yml
# ...
services:
_defaults:
autowire: true
autoconfigure: true
+ public: false
Завдяки цьому, будь-які сервіси, створені в цьому файлі, не можуть бути викликані безпосередньо
з контейнера. Однак, оскільки старі id сервісу є псевдонімами в окремому файлі
(legacy_aliases.yml
), вони все ще є публічними. Таким чином додаток
продовжує працювати.
Якщо ви не змінили id деяких із ваших сервісів (тому що вони є множиною екземплярів одного класу), вам може знадобитися зробити їх публічними:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# app/config/services.yml
# ...
services:
# ...
app.api_client_github:
# ...
+ # видаліть це якщо/коли ви не викликаєте це
+ # напряму з контейнера через $container->get()
+ public: true
app.api_client_sl_connect:
# ...
+ public: true
Це для того, щоб гарантувати, що додаток не зламається. Якщо ви не викликаєте ці сервіси напряму з контейнера - це не потрібно. За хвилину ви це очистите.
Крок 4) Автореєстрація сервісів
Тепер ви готові автоматично реєструвати всі сервіси в src/AppBundle/
(та/або будь-якому іншому каталозі/пакеті, який у вас є):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# app/config/services.yml
services:
_defaults:
# ...
+ AppBundle\:
+ resource: '../../src/AppBundle/*'
+ exclude: '../../src/AppBundle/{Entity,Repository}'
+
+ AppBundle\Controller\:
+ resource: '../../src/AppBundle/Controller'
+ public: true
+ tags: ['controller.service_arguments']
# ...
Ось і все! Насправді, ви вже перевизначаєте і переконфігуруєте всі сервіси,
які ви використовуєте (AppBundle\Service\GitHubNotifier
і
AppBundle\Service\MarkdownTransformer
). Але тепер, вам не потрібно буде вручну
реєструвати майбутні сервіси.
Ще раз, існує додаткове ускладнення, якщо у вас є багато сервісів одного класу:
1 2 3 4 5 6 7 8 9 10 11 12 13
# app/config/services.yml
services:
# ...
+ # псевдонім ApiClient одного з ваших сервісів нижче
+ # app.api_client_github буде використано для автомонтування типізувань ApiClient
+ AppBundle\Service\ApiClient: '@app.api_client_github'
app.api_client_github:
# ...
app.api_client_sl_connect:
# ...
Це гарантує, що якщо ви спробуєте автоматично змонтувати екземпляр ApiClient
,
буде використано app.api_client_github
. Якщо у вас цього немає, функція автореєстрації
спробує зареєструвати третій сервіс ApiClient
і використати його для автомонтування
(яке не вдасться, оскільки клас має аргумент, що не піддається автомонтуванню).
Крок 5) Очищення!
Щоб переконатися в тому, що ваш додаток не зламався, ви виконали зайву роботу.
Тепер настав час трохи прибратися! Для початку, оновіть ваш додаток так, щоб він не
використовував старі id сервісів (ті, які в legacy_aliases.yml
). Це означає
оновлення будь-яких аргументів сервісу (наприклад, з @app.github_notifier
на
@AppBundle\Service\GitHubNotifier
) і оновлення вашого коду так, щоб він не викликав
цей сервіс безпосередньо з контейнера. Наприклад:
1 2 3 4 5 6 7 8 9
- public function indexAction()
+ public function indexAction(GitHubNotifier $gitHubNotifier, MarkdownTransformer $markdownTransformer)
{
- // старий спосіб виклику сервісів
- $githubNotifier = $this->container->get('app.github_notifier');
- $markdownTransformer = $this->container->get('markdown_transformer');
// ...
}
Як тільки ви це зробите, ви можете видалити legacy_aliases.yml
і видалити його
імпорт. Ви повинні зробити те саме з будь-якими сервісами, які ви зробили публічними,
такими як app.api_client_github
і app.api_client_sl_connect
. Як тільки ви
перестанете викликати їх безпосередньо з контейнера, ви можете видалити мітку public: true
:
1 2 3 4 5 6 7 8 9 10 11
# app/config/services.yml
services:
# ...
app.api_client_github:
# ...
- public: true
app.api_client_sl_connect:
# ...
- public: true
Нарешті, ви можете за бажанням видалити будь-які сервіси з services.yml
, аргументи
яких можуть бути автоматично змонтовані. Фінальна конфігурація виглядає так:
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
services:
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository}'
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
AppBundle\Service\GitHubNotifier:
# це можна видалити, або може бути ясним
arguments:
- '@app.api_client_github'
# псевдонім ApiClient одного з наших сервісів нижче
# app.api_client_github буде використано для автомонтування типізувань ApiClient
AppBundle\Service\ApiClient: '@app.api_client_github'
# залиште ці id, так як існує багато екземплярів одного класу
app.api_client_github:
class: AppBundle\Service\ApiClient
arguments:
- 'https://api.github.com'
app.api_client_sl_connect:
class: AppBundle\Service\ApiClient
arguments:
- 'https://connect.sensiolabs.com/api'
Тепер ви можете користуватися перевагами нових функцій у майбутньому.