12.15. Ծառայություններ և կախվածության ներարկում:
Երբ մենք օգտագործում ենք Drupal և մեզ անհրաժեշտ է օգտագործել contrib մոդուլի կամ միջուկային մոդուլի կոդը custom մոդուլի մեջ, ապա մենք օգտագործում ենք hook-եր և services (սերվիսներ): Մենք արդեն օգտագործել ենք hook-եր այս հոդվածում․
12.11.3. Entity-ների հետ աշխատանքի hook-եր։
Եկեք այժմ հասկանանք՝ ինչ է service-ը։ Service-ը PHP օբյեկտ է։ Հետեւաբար երբ դուք ստեղծում եք նոր PHP դաս custom մոդուլում, ավելի լավ է այն անմիջապես կազմաձեւել որպես service, որպեսզի հետագայում այդ կոդը հնարավոր լինի օգտագործել այլ մոդուլում՝ ստանդարտ եղանակով։
Drupal-ը հավաքում է բոլոր սերվիսները Service Container անունով PHP օբյեկտի մեջ, այսինքն՝ ամբողջ տեղեկությունը հասանելի և օգտագործվող սերվիսների մասին պահվում է մեկ վայրում։ Դուք կարող եք կանչել այս օբյեկտը և տեսնել, թե ինչ սերվիսներ եք օգտագործում՝
<?php
$container = \Drupal::getContainer();
?>
https://api.drupal.org/api/drupal/core!lib!Drupal.php/function/Drupal%3A%3AgetContainer/9.2.x
Դուք կարող եք աշխատել այս օբյեկտի հետ has/get/set մեթոդների միջոցով, սակայն սովորաբար սերվիսներ ավելացնում ենք *.services.yml ֆայլերում։
Եկեք դիտենք getContainer() մեթոդի իրականացման օրինակ․
<?php
public static function getContainer() {
if (static::$container === NULL) {
throw new ContainerNotInitializedException('\\Drupal::$container is not initialized yet. \\Drupal::setContainer() must be called with a real container.');
}
return static::$container;
}
?>
Service կոնտեյների փոփոխականը հայտարարված է որպես ստատիկ, ինչը նշանակում է՝ index.php ֆայլի կանչից հետո և մինչև ավարտը մենք կարող ենք ցանկացած պահի այդ արժեքը վերցնել ցանկացած ֆայլից՝ hook, class կամ .theme ֆայլից։
Ինչպես օգտագործել սերվիսները Drupal-ում՞
Եկեք դիտարկենք՝ ինչպես օգտագործել Service Container-ը Drupal-ում։ $container օբյեկտում պահվում են սերվիսների օբյեկտներ, որոնք թույլ են տալիս անհրաժեշտ ամբողջ տրամաբանությունը իրականացնել կոնստրուկտորի մակարդակում և փոխանցել արդեն պատրաստ օբյեկտը մեր custom մոդուլին։ Օրինակ՝ պետք է SQL հարցում գրենք բազային, մենք պարզապես կանչում ենք տվյալների բազայի աշխատող սերվիսը, որն արդեն օգտագործել է settings.php-ում հայտարարված մուտքանունն ու գաղտնաբառը և կապ է հաստատել MySQL-ի հետ՝ մեր SQL հարցման համար․
$query = \Drupal::database()->select('node_field_data', 'n');
$query->addField('n', 'nid');
$query->condition('n.title', 'About Us');
$query->range(0, 1);
$nid = $query->execute()->fetchField();
Եթե դիտեք database() մեթոդի իրականացումը, կտեսնեք, որ մենք օգտագործում ենք database սերվիսի օբյեկտը Service Container-ից․
https://api.drupal.org/api/drupal/core%21lib%21Drupal.php/function/Drupal%3A%3Adatabase/9.2.x
<?php
public static function database() {
return static::getContainer()
->get('database');
}
?>
Այսպիսով մենք միացնում ենք միայն այն դասերը, որոնք անհրաժեշտ են մեր կոնկրետ կոդի համար։ Դրա համար էլ օգտագործում ենք միասնական պահեստ՝ Service Container-ը։
Ինչպես ավելացնել սերվիս Service Container-ում՞
Երբ մենք ստեղծում ենք *.services.yml ֆայլ, Drupal-ը բեռնում է այդ ֆայլից սերվիսները և պահում է դրանց օբյեկտները Service Container-ում։
https://api.drupal.org/api/drupal/core%21modules%21syslog%21syslog.services.yml/9.2.x
core/modules/syslog/syslog.services.yml:
services:
logger.syslog:
class: Drupal\syslog\Logger\SysLog
arguments: ['@config.factory', '@logger.log_message_parser']
tags:
- { name: logger }
$container փոփոխականում կարելի է ավելացնել սերվիս set() մեթոդի միջոցով, սակայն դա հիմնականում օգտագործվում է unit test-երում mocking-ի համար՝
Ի՞նչ է Dependency Injection-ը
Եթե գործարկեք Code Sniffer-ը, ապա կստանաք սխալ, որ Drupal::database()-ը պետք է տեղափոխել դասի կոնստրուկտոր։ Երբ դուք կանչում եք սերվիս կոնստրուկտորի մեջ Service Container-ից, դա կոչվում է Dependency Injection (DI)։ Օրինակ՝
...
* Implements course save handler.
*
* Function for save course data in course content type.
*/
Այս օրինակում տվյալների բազայի հարցումը անհրաժեշտ է ձևի մեջ, ուստի մենք ավելացրել ենք create() մեթոդ, որն օգտագործվում է դասի օրինակ ստեղծելու համար։ Այս մեթոդը որպես պարամետր ստանում է $container փոփոխականը՝ ContainerInterface տիպով։ Եթե այդ մեթոդում կանչվում է սերվիս $container->get('myservice.name') եղանակով, ապա այդ օբյեկտը փոխանցվում է կոնստրուկտորին որպես արգումենտ։
Հաջորդ հոդվածներում մենք կծանոթանանք թե ինչպես ճիշտ օգտագործել սերվիսները կոնստրուկտորում՝ Controller, Block, BaseForm, ConfigForm կամ custom դասերում։
Այնուհետև կսովորենք ինչպես ստեղծել մեր սեփական սերվիսները։
Նաեւ կսովորենք ինչպես վերագրել (override) սերվիսի դասը՝ contrib մոդուլի փոխարեն օգտագործելու custom մոդուլի դաս։
Ինչու՞ են անհրաժեշտ Service Container-ը և Dependency Injection-ը
Մենք կարող էինք ուղղակիորեն օգտագործել namespaces և միացնել մոդուլների կոդը՝ օբյեկտների ստեղծումով։ Բայց դա առաջացնում է թարմացման դժվարություններ։ Օրինակ՝ պետք է փոխարինել email ուղարկող դասը, որը օգտագործվում է 200 տեղ։ Մենք ստեղծում ենք սերվիս, ու երբ պետք է փոխենք ուղարկման եղանակը՝ օրինակ smtp-ի, ապա պարզապես փոխում ենք սերվիսի դասը։
Dependency Injection-ը օգնում է խուսափել նույն սերվիսին բազմակի կանչից։ Մենք ուղղակի պահում ենք սերվիսի օբյեկտը դասի հատկության մեջ և օգտագործում ենք այն $this->serviceName տեսքով։
Թեեւ կարելի է աշխատել առանց Service Container և Dependency Injection-ի, բայց դրանք ստանդարտացնում են մեր կոդը և հեշտացնում են դրա կառավարումը ու թարմացումը։
Որտե՞ղ տեսնել սերվիսի անունը
Օրինակ՝ մենք օգտագործում ենք "database" սերվիսը․
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
// Այստեղ ավելացնում ենք սերվիսի անունը։
$container->get('database'),
);
}
իսկ եթե սերվիսը contrib/custom մոդուլից է, ապա այն կարող է ունենալ նմանատիպ անուն՝
module_name.service_name
Դուք կարող եք ստուգել սերվիսի անունը *.services.yml ֆայլում։ Անվան սկզբում սովորաբար գրվում է մոդուլի անունը, բայց դա պարտադիր չէ։ Օրինակ՝
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('commerce_cart.cart_provider'),
$container->get('entity_type.manager')
);
}