PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Service Contracts – Data Interfaces i Repository Pattern

by Henryk Tews / wtorek, 12 lutego 2019 / Opublikowano w Magento 2


Bezpośrednie użycie modeli Magento 2 w swoim kodzie to kusząca droga na skróty. Model jest pod ręką, ma metodę której szukasz, działa. Problem pojawia się przy aktualizacji Magento, gdy wewnętrzna implementacja modelu się zmienia. Service Contracts to odpowiedź Magento na ten problem – stabilne, publiczne API oddzielone od implementacji.

Czym są Service Contracts?

Service Contract to zestaw interfejsów PHP definiujących publiczne API modułu. Magento dzieli je na dwa typy:

  • Data Interfaces – opisują strukturę danych (np. ProductInterface, OrderInterface)
  • Service Interfaces – opisują operacje biznesowe (np. ProductRepositoryInterface, OrderManagementInterface)

Interfejsy oznaczone adnotacją @api są objęte gwarancją wstecznej kompatybilności przez Magento. Implementacja może się zmienić – kontrakt pozostaje stabilny.

Model vs Repository – konkretny przykład

Ładowanie produktu bez Service Contracts:

<?php

// Sposób "na skróty" – bezpośrednie użycie modelu
class MyClass
{
    private \Magento\Catalog\Model\ProductFactory $productFactory;

    public function __construct(
        \Magento\Catalog\Model\ProductFactory $productFactory
    ) {
        $this->productFactory = $productFactory;
    }

    public function getProduct(int $id): \Magento\Catalog\Model\Product
    {
        $product = $this->productFactory->create();
        $product->load($id); // metoda load() jest deprecated od Magento 2.1!

        return $product;
    }
}

Ten sam efekt przez Service Contract:

<?php

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Api\Data\ProductInterface;

class MyClass
{
    private ProductRepositoryInterface $productRepository;

    public function __construct(
        ProductRepositoryInterface $productRepository
    ) {
        $this->productRepository = $productRepository;
    }

    public function getProduct(int $id): ProductInterface
    {
        // Rzuca NoSuchEntityException jeśli produkt nie istnieje
        return $this->productRepository->getById($id);
    }
}

Różnice są istotne: type hinty opierają się na interfejsach (nie konkretnych klasach), metoda getById() jest częścią stabilnego API, a wyjątek NoSuchEntityException jest jawnie zadeklarowany w kontrakcie.

Data Interfaces – praca z danymi przez gettery i settery

ProductInterface definiuje zestaw getterów i setterów dla standardowych atrybutów produktu:

<?php

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;

class ProductService
{
    public function __construct(
        private ProductRepositoryInterface $productRepository,
        private SearchCriteriaBuilder $searchCriteriaBuilder
    ) {}

    public function getActiveProducts(): array
    {
        $searchCriteria = $this->searchCriteriaBuilder
            ->addFilter('status', 1)
            ->addFilter('visibility', [2, 3, 4], 'in')
            ->setPageSize(20)
            ->create();

        $result = $this->productRepository->getList($searchCriteria);

        return array_map(
            fn(ProductInterface $p) => [
                'sku'   => $p->getSku(),
                'name'  => $p->getName(),
                'price' => $p->getPrice(),
            ],
            $result->getItems()
        );
    }
}

Własne Service Contracts w module

Pisząc własny moduł, warto zdefiniować Service Contracts dla jego głównych encji. Struktura katalogów:

Vendor/Module/
  Api/
    Data/
      SubscriptionInterface.php   <- Data Interface
    SubscriptionRepositoryInterface.php  <- Service Interface
  Model/
    Subscription.php              <- implementacja modelu
    SubscriptionRepository.php    <- implementacja repository

Przykład Data Interface:

<?php

namespace Vendor\Module\Api\Data;

/**
 * @api
 */
interface SubscriptionInterface
{
    public const FIELD_ID         = 'subscription_id';
    public const FIELD_EMAIL      = 'email';
    public const FIELD_CREATED_AT = 'created_at';

    public function getId(): ?int;
    public function getEmail(): string;
    public function setEmail(string $email): self;
    public function getCreatedAt(): string;
}

Rejestracja implementacji przez preference w di.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Vendor\Module\Api\Data\SubscriptionInterface"
                type="Vendor\Module\Model\Subscription"/>
    <preference for="Vendor\Module\Api\SubscriptionRepositoryInterface"
                type="Vendor\Module\Model\SubscriptionRepository"/>
</config>

Zwróć uwagę, że tu preference jest uzasadnione - wiążemy interfejs z implementacją. To właśnie ten przypadek, o którym pisałem we wpisie o Plugin vs Preference.

Podsumowanie

Service Contracts to nie biurokratyczny narzut, ale realna ochrona przed problemami przy aktualizacjach Magento. Kod oparty na interfejsach @api jest bardziej odporny na zmiany wewnętrzne platformy i łatwiejszy do testowania – możesz podmienić implementację repozytorium na mocka bez zmiany kodu korzystającego z kontraktu.

About Henryk Tews

Co możesz przeczytać następne

Akeneo PIM – po co PIM, REST API, mapowanie atrybutów, import przez cron
Redis Streams – Consumer Groups, pending messages, dead letter, integracja z Magento queue
Optymalizacja wydajności – OPcache, Redis, N+1 queries, Blackfire, tabela priorytetów
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Zawsze aktywne
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}