logo

Dodatni tipovi blokova (EBT) - Novo iskustvo rada sa Layout Builder-om❗

Dodatni tipovi blokova (EBT) – stilizovani, prilagodljivi tipovi blokova: slajdšouvi, kartice sa tabovima, kartice, akordeoni i mnogi drugi. Ugrađena podešavanja za pozadinu, DOM Box, javascript dodatke. Iskusite budućnost kreiranja rasporeda već danas.

Demo EBT moduli Preuzmite EBT module

❗Dodatni tipovi pasusa (EPT) – Novo iskustvo rada sa pasusima

Dodatni tipovi pasusa (EPT) – analogni skup modula zasnovan na pasusima.

Demo EPT moduli Preuzmite EPT module

Scroll

12.15. Servisi i Dependency Injection.

25/05/2025, by Ivan

Menu

Kada koristimo Drupal i treba nam kod iz contrib modula ili modula jezgra u našem prilagođenom modulu, koristimo hook-ove i servise (services). Već smo koristili hook-ove u ovom članku:

12.11.3. Hook-ovi za rad sa Entity.

Sada da se pozabavimo servisima. Servis je PHP objekat. Zato kada pravite novu PHP klasu u svom prilagođenom modulu, bolje je odmah je dizajnirati kao servis, kako bi vaš kod kasnije mogao da se koristi u drugom modulu na standardan način.

Drupal skuplja sve servise u PHP objektu zvanom Service Container, tako da Drupal čuva informacije o svim dostupnim i korišćenim servisima na jednom mestu. Možete pozvati ovaj objekat i videti koji se servisi koriste:

<?php
$container = \Drupal::getContainer();
?>

https://api.drupal.org/api/drupal/core!lib!Drupal.php/function/Drupal%3A%3AgetContainer/9.2.x

Get container

Service container

Možete raditi sa ovim objektom koristeći metode has/get/set, ali obično ćemo dodavati servise u kontejner pomoću *.services.yml fajlova u našim modulima.

Hajde da pogledamo implementaciju metode 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;
}
?>

Promenljiva Service Container je definisana kao statička, što znači da posle poziva index.php i do kraja obrade bilo kog zahteva možemo u bilo kom fajlu tokom izvršavanja pristupiti vrednosti ove promenljive: to može biti bilo koja klasa, hook u modulu ili čak .theme fajl teme.

Kako koristiti servise u Drupalu?

Sada da pređemo na to kako koristiti Service Container u Drupalu. U objektu $container nalaze se objekti servisa, što omogućava da se sva neophodna logika za kreiranje objekta izvrši u konstruktoru i da dobijemo spreman za korišćenje objekat u našem prilagođenom modulu. Na primer, ako treba da napišemo SQL upit bazi, samo pozovemo objekat za rad sa bazom iz Service Container-a koji već koristi kredencijale iz našeg settings.php i uspostavlja konekciju sa MySQL prilikom izvršenja upita:

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

Ako pogledate implementaciju metode database(), videćete da koristimo servis database iz Service Container-a:

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');
}
?>

Na ovaj način učitavamo samo one klase koje nam u datom trenutku trebaju. Zato koristimo jedinstveno skladište objekata - Service Container.

Kako dodati servis u Service Container?

Kada kreiramo fajl *.services.yml, Drupal učitava servise iz tih fajlova i čuva njihove objekte u Service Container-u.

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 }

U promenljivu $container možete dodati servis metodom set(), ali to se obično koristi za pravljenje mokova (mocking) u testovima:

https://www.drupal.org/docs/automated-testing/phpunit-in-drupal/mocking-entities-and-services-with-phpunit-and-mocks

Šta je Dependency Injection?

Ako pokrenete Code Sniffer, dobićete grešku da treba izmeniti Drupal::database() tako da se database poziva u konstruktoru klase u kojoj koristimo objekat iz Service Container-a. Kada pozivate objekat iz Service Container-a u konstruktoru klase, to se zove Dependency Injection (DI), na primer:

<?php

namespace Drupal\wisenet_connect\Form;

use Drupal\Core\Database\Connection;

/**
 * Implementira WisenetConfigurationForm form kontroler.
 *
 * Ovaj primer pokazuje jednostavan formular sa jednim tekstualnim unosom.
 * Proširujemo FormBase, što je najjednostavnija osnovna klasa za formu u Drupalu.
 *
 * @see \Drupal\Core\Form\FormBase
 */
class WisenetGetCourseForm extends FormBase {

  /**
   * Aktivna konekcija ka bazi.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;


  /**
   * Konstruktor WisenetGetCourseForm objekta.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   Konekcija ka bazi koja se koristi.
   */
  public function __construct(Connection $database) {
    $this->database = $database;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('database'),
    );
  }

 ...

  /**
   * Implementira handler za čuvanje kursa.
   *
   * Funkcija za čuvanje podataka kursa u content type kursa.
   */
  public function saveCourse($courses) {
     ...
      $query = $this->database->select('node__field_course_wisenet_id', 'nc');
      $query->addField('n', 'nid');
      $query->join('node_field_data', 'n', 'nc.entity_id = n.nid');
      $query->condition('nc.field_course_wisenet_id_value', $course['CourseOfferId']);
      $query->range(0, 1);

      $nid = $query->execute()->fetchField();
     ...
  }

U ovom primeru, upit ka bazi je potreban u formi, pa smo dodali dodatni metod create(), koji se koristi za kreiranje instance klase. Metod create() može biti u različitim klasama i interfejsima, ali uvek ima parametar $container tipa ContainerInterface. Ako se u metodu create() pozove objekat iz Service Container-a $container->get('myservice.name'), vraćeni objekat se prosleđuje u konstruktor __construct() kao argument (u našem primeru $container->get('database') i argument Connection $database).

O tome kako treba pravilno pozivati objekte iz Service Container-a u konstruktoru kontrolera (Controller), bloka (Block), forme (BaseForm), konfiguracione forme (ConfigForm) prilagođene klase/servisa, obradićemo u narednim člancima.

Nakon što prođemo kako pravilno povezati i koristiti objekte iz Service Container-a, razmotrićemo i kako praviti svoje servise.

Takođe ćemo pokazati kako preklapati klase za servise, da bismo umesto klase iz contrib modula koristili klasu iz prilagođenog modula.

Zašto su potrebni Service Container i Dependency Injection?

Možemo koristiti namespaces i direktno pozvati kod iz modula, praviti instance za spoljne klase gde nam treba bez korišćenja Service Container-a. Ali to izaziva probleme kod održavanja koda. Na primer, treba da zamenimo klasu koja šalje email poruke, a ta klasa se poziva na 200 različitih mesta. Radi lakšeg održavanja koda pravimo servis, a ne direktno uključivanje fajlova. Kada želimo da šaljemo email preko SMTP umesto PHP mail(), samo menjamo klasu servisa, a ne putanju do nove klase na 200 mesta.

Dependency Injection rešava problem dvostrukog poziva servisa u jednoj klasi. Ne moramo dva puta pristupati Service Container-u ako koristimo servis u različitim metodama iste klase. Jednostavno zapišemo servis objekat u svojstvo naše klase i koristimo ga iz tog svojstva preko $this->serviceName.

Naravno, možemo funkcionisati i bez Service Container-a i Dependency Injection-a, ali ovi obrasci unifikuju naš kod i čine ga jednostavnijim za održavanje i ažuriranje.

Gde mogu videti ime servisa?

U našem primeru imamo servis "database":

/**
 * {@inheritdoc}
 */
public static function create(ContainerInterface $container) {
  return new static(
    // Ovde dodajemo ime servisa.
    $container->get('database'),
  );
}

Ali ako dodate servis iz contrib ili custom modula, on može imati ime:

ime_modula.ime_servisa

Možete proveriti ime servisa u fajlu *.services.yml. Ime servisa ne mora uvek da počinje sa module_name.*, ali obično je tako. Na primer:

/**
 * {@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')
  );
}