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

Korišćenje PHP Constructor Property Promotion u prilagođenim Drupal modulima

13/06/2025, by Ivan

Menu

PHP 8 je uveo constructor property promotion, funkcionalnost koja pojednostavljuje definisanje i dodelu svojstava klase tako što omogućava da ih deklarišete i inicijalizujete direktno u potpisu konstruktora. Ovaj vodič prikazuje kako koristiti constructor property promotion u prilagođenim Drupal modulima (koji zahtevaju PHP 8.0+), posebno za pojednostavljenje dependency injection-a u vašim servisima i kontrolerima. Uporedićemo tradicionalni Drupal obrazac (koji se koristi u PHP 7 i ranim verzijama Drupal 9) sa savremenim PHP 8+ pristupom, uz potpune primere koda za oba. Na kraju, videćete kako ovaj savremeni sintaksis smanjuje količinu šablonskog koda, čini kod jasnijim i usklađuje se sa aktuelnim najboljim praksama.

Drupal 10 (koji zahteva PHP 8.1+) je počeo da usvaja ove moderne PHP funkcionalnosti u core-u, tako da se programerima prilagođenih modula preporučuje isto. Hajde da prvo pregledamo tradicionalni obrazac dependency injection-a u Drupalu, a zatim ga refaktorišemo korišćenjem constructor property promotion.

Tradicionalni Dependency Injection u Drupalu (pre-PHP 8)

U Drupal servisima i kontrolerima, tradicionalni obrazac za dependency injection uključuje tri koraka:

  1. Deklarišite svaku zavisnost kao svojstvo klase (obično protected) sa odgovarajućim docblock-om.

  2. Tipizirajte svaku zavisnost u parametrima konstruktora i dodelite ih svojstvima klase unutar konstruktora.

  3. Za kontrolere (i neke plugin klase), implementirajte statički metod create(ContainerInterface $container) da preuzmete servise iz Drupal-ovog service container-a i instancirate klasu.

Ovo dovodi do poprilično mnogo šablonskog koda. Na primer, razmotrite jednostavan prilagođeni servis kojem su potrebni configuration factory i logger factory. Tradicionalno, napisali biste:

Primer tradicionalne servis klase

<?php

namespace Drupal\example;

/**
 * Primer servisa koji loguje naziv sajta.
 */
class ExampleService {
  /**
   * Servis configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * Servis logger channel factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerFactory;

  /**
   * Konstruktor ExampleService objekta.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   Configuration factory.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   Logger channel factory.
   */
  public function __construct(ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory) {
    // Čuvanje ubačenih servisa.
    $this->configFactory = $config_factory;
    $this->loggerFactory = $logger_factory;
  }

  /**
   * Loguje naziv sajta kao primer akcije.
   */
  public function logSiteName(): void {
    $site_name = $this->configFactory->get('system.site')->get('name');
    $this->loggerFactory->get('example')->info('Site name: ' . $site_name);
  }
}

Ovde deklarišemo dva svojstva $configFactory i $loggerFactory i dodeljujemo ih u konstruktoru. Odgovarajući servis takođe mora biti definisan u services YAML fajlu modula (sa servisima koji su mu potrebni kao argumenti), na primer:

# example.services.yml
services:
  example.example_service:
    class: Drupal\example\ExampleService
    arguments:
      - '@config.factory'
      - '@logger.factory'

Kada Drupal instancira ovaj servis, proslediće podešene argumente konstruktoru redom kojim su navedeni.

Primer tradicionalne kontroler klase

Drupal kontroleri takođe mogu koristiti dependency injection. Tipično, klasa kontrolera nasleđuje ControllerBase (zbog pogodnosti) i implementira Drupal-ov container injection definisanjem create() metode. Metoda create() je fabrika koja preuzima servise iz container-a i poziva konstruktor. Na primer:

<?php

namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Kontroler za Example rute.
 */
class ExampleController extends ControllerBase {
  /**
   * Servis entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Servis string translation.
   *
   * @var \Drupal\Core\StringTranslation\TranslationInterface
   */
  protected $stringTranslation;

  /**
   * Konstruktor ExampleController objekta.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   Entity type manager.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   *   String translation servis.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
    $this->entityTypeManager = $entity_type_manager;
    $this->stringTranslation = $string_translation;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    // Preuzimanje potrebnih servisa iz container-a i prosleđivanje konstruktoru.
    return new self(
      $container->get('entity_type.manager'),
      $container->get('string_translation')
    );
  }

  /**
   * Pravi jednostavan page response.
   */
  public function build(): array {
    // Primer korišćenja ubačenih servisa.
    $node_count = $this->entityTypeManager->getStorage('node')->getQuery()->count()->execute();
    return [
      '#markup' => $this->t('Na sajtu ima @count čvorova.', ['@count' => $node_count]),
    ];
  }
}

Korišćenje Constructor Property Promotion (PHP 8+) u Drupalu

Constructor property promotion pojednostavljuje prethodni obrazac tako što omogućava da deklarišete i dodelite svojstva u jednom koraku, direktno u potpisu konstruktora. U PHP 8, možete navesti vidljivost (i druge modifikatore kao što je readonly) uz parametre konstruktora, i PHP će automatski kreirati i dodeliti ta svojstva za vas. Ovo znači da više ne morate posebno da deklarišete svojstvo ili pišete dodelu unutar konstruktora – PHP to radi umesto vas.

Važno je da je ovo syntactic sugar. Ne menja način na koji Drupal-ov dependency injection radi; jednostavno smanjuje količinu koda koju pišete. I dalje registrujete servise u YAML fajlu (ili prepustite Drupalu autowiring), a za kontrolere i dalje koristite create() factory metodu (osim ako kontroler registrujete kao servis). Razlika je samo u tome kako pišete kod klase. Rezultat je znatno manje šablonskog koda, kao što pokazuje i Drupal core issue gde su desetine linija deklaracija i dodela svedene na svega par linija u konstruktoru.

Hajde da refaktorišemo naše primere koristeći constructor property promotion.

Servis klasa sa Constructor Property Promotion

Evo ExampleService klase prepravljene korišćenjem PHP 8 promoted properties sintakse:

<?php

namespace Drupal\example;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;

/**
 * Primer servisa koji loguje naziv sajta (koristi property promotion).
 */
class ExampleService {
  /**
   * Konstruktor ExampleService objekta sa ubačenim servisima.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Configuration factory.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   Logger channel factory.
   */
  public function __construct(
    protected ConfigFactoryInterface $configFactory,
    protected LoggerChannelFactoryInterface $loggerFactory
  ) {
    // Nema potrebe za telom; svojstva se automatski postavljaju.
  }

  /**
   * Loguje naziv sajta kao primer akcije.
   */
  public function logSiteName(): void {
    $site_name = $this->configFactory->get('system.site')->get('name');
    $this->loggerFactory->get('example')->info('Site name: ' . $site_name);
  }
}

Kontroler klasa sa Constructor Property Promotion

Sada, pogledajmo ExampleController refaktorisanu da koristi promoted properties:

<?php

namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Kontroler za Example rute (koristi property promotion).
 */
final class ExampleController extends ControllerBase {
  /**
   * Konstruktor ExampleController-a.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation
   *   String translation servis.
   */
  public function __construct(
    private EntityTypeManagerInterface $entityTypeManager,
    private TranslationInterface $stringTranslation
  ) {
    // Nema potrebe za dodelama; svojstva se postavljaju automatski.
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    // Prosleđivanje servisa iz container-a konstruktoru.
    return new self(
      $container->get('entity_type.manager'),
      $container->get('string_translation')
    );
  }

  /**
   * Pravi jednostavan page response.
   */
  public function build(): array {
    $node_count = $this->entityTypeManager->getStorage('node')->getQuery()->count()->execute();
    return [
      '#markup' => $this->t('Na sajtu ima @count čvorova.', ['@count' => $node_count]),
    ];
  }
}

Prednosti Constructor Property Promotion-a

Korišćenje constructor property promotion-a u vašim Drupal klasama donosi nekoliko prednosti:

  • Manje šablonskog koda: Pišete znatno manje koda. Nema potrebe za ručnim deklarisanjem i dodelom svojstava, što može ukloniti mnogo linija koda, posebno kada klasa ima više zavisnosti. To čini module čistijim i lakšim za održavanje.

  • Jasniji i sažetiji kod: Sve zavisnosti klase su vidljive na jednom mestu – u potpisu konstruktora – umesto da su rasute između deklaracija svojstava i tela konstruktora. To poboljšava čitljivost i odmah je jasno koji servisi su potrebni klasi.

  • Manje doc komentara: Pošto su svojstva deklarisana sa tipovima u konstruktoru, možete izostaviti suvišne @var i @param anotacije za ta svojstva i parametre konstruktora (pod uslovom da je svrha jasna iz naziva). Kod je u velikoj meri samodokumentovan. I dalje možete dokumentovati sve što nije očigledno, ali ima manje ponavljanja.

  • Savremena PHP sintaksa: Usvajanjem property promotion-a vaš kod ostaje u skladu sa savremenim PHP praksama. Drupal 10+ core je počeo da koristi ovu sintaksu za novi kod, pa korišćenje iste u prilagođenim modulima čini vaš kod konzistentnijim sa core-om i primerima iz zajednice. Takođe, priprema vaš codebase za buduća unapređenja (na primer, PHP 8.1+ omogućava upotrebu readonly ključa za zaista nepromenljive zavisnosti).

Performanse i funkcionalnost ostaju iste kao kod tradicionalnog injection-a – property promotion je isključivo pogodnost u jeziku. I dalje dobijate potpuno tipizirana svojstva koja možete koristiti kroz celu klasu (npr. $this->entityTypeManager u primeru kontrolera). U pozadini, rezultat je ekvivalentan dužem kodu; samo je napisan sa manje napora.

Zaključak

Constructor property promotion je jednostavna ali moćna PHP 8 funkcionalnost koju Drupal developeri mogu iskoristiti da pojednostave razvoj prilagođenih modula. Eliminisanjem šablonskog koda, možete se fokusirati na ono što vaša klasa zapravo radi, umesto na povezivanje servisa. Prikazali smo kako da tipičnu servis klasu i kontroler klasu u Drupalu pretvorite da koriste promoted properties, i uporedili ih sa tradicionalnim pristupom. Rezultat je sažetiji i lakši za održavanje kod bez gubitka jasnoće ili funkcionalnosti. Kako Drupal prelazi na savremene PHP zahteve, korišćenje funkcionalnosti poput property promotion-a u vašim prilagođenim modulima pomoći će da vaš kod ostane čist, jasan i u skladu sa najboljim praksama. Usvojite savremenu sintaksu da učinite svoj Drupal razvoj lakšim i elegantnijim.