Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Data interfaces

A service contract is a set of PHP interfaces through which other modules work with your data. An interface promises: these methods will not change even if the table or the model behind them is rewritten. A contract has two parts: data interfaces describe a record, and repositories describe actions on it. This lesson covers the first part.

Where interfaces live

All contract interfaces are in the module's Api folder, data interfaces in Api/Data. The files are created by hand.

app/code/Acme/Hello/
├── Api/
│   └── Data/
│       └── ReviewInterface.php     new file
├── Model/
│   └── Review.php                  changed
└── etc/
    └── di.xml                      changed

The data interface

The interface lists a getter and a setter for every field:

// app/code/Acme/Hello/Api/Data/ReviewInterface.php
namespace Acme\Hello\Api\Data;
 
interface ReviewInterface
{
    /** @return int|null */
    public function getId();
 
    /** @return int */
    public function getProductId();
 
    /**
     * @param int $productId
     * @return $this
     */
    public function setProductId($productId);
 
    /** @return string */
    public function getAuthor();
 
    /**
     * @param string $author
     * @return $this
     */
    public function setAuthor($author);
 
    /** @return int */
    public function getRating();
 
    /**
     * @param int $rating
     * @return $this
     */
    public function setRating($rating);
}

The @param and @return comments are required here: Magento uses them to turn an object into JSON and back when the contract is exposed through the API. The content field is added with the same pair of methods.

Implementing it in the model

The model from the previous lesson declares that it fulfils the contract and gets explicit methods:

// app/code/Acme/Hello/Model/Review.php
namespace Acme\Hello\Model;
 
use Acme\Hello\Api\Data\ReviewInterface;
use Magento\Framework\Model\AbstractModel;
 
class Review extends AbstractModel implements ReviewInterface
{
    protected function _construct(): void
    {
        $this->_init(ResourceModel\Review::class);
    }
 
    public function getProductId()
    {
        return (int) $this->getData('product_id');
    }
 
    public function setProductId($productId)
    {
        return $this->setData('product_id', (int) $productId);
    }
 
    public function getAuthor()
    {
        return (string) $this->getData('author');
    }
 
    public function setAuthor($author)
    {
        return $this->setData('author', (string) $author);
    }
 
    public function getRating()
    {
        return (int) $this->getData('rating');
    }
 
    public function setRating($rating)
    {
        return $this->setData('rating', (int) $rating);
    }
}

AbstractModel already has the getId() method. The others read and write column values through getData() and setData().

The preference

For Magento to know which class stands behind the interface, add a line to etc/di.xml:

<!-- app/code/Acme/Hello/etc/di.xml -->
<preference for="Acme\Hello\Api\Data\ReviewInterface" type="Acme\Hello\Model\Review"/>

Run bin/magento cache:clean. Nothing changes on the page — what changed is the way the rest of the code talks to a review.

Using it

Now code depends on the interface, not on the model. A new empty review comes from the generated interface factory:

// app/code/Acme/Hello/Model/ReviewService.php
use Acme\Hello\Api\Data\ReviewInterfaceFactory;
 
public function __construct(private ReviewInterfaceFactory $reviewFactory) {}
 
public function draft(int $productId, string $author): \Acme\Hello\Api\Data\ReviewInterface
{
    return $this->reviewFactory->create()
        ->setProductId($productId)
        ->setAuthor($author)
        ->setRating(5);
}

The method returns a filled but not yet saved review. The repository from the next lesson saves it.

What this gives you

Without a contractWith a contract
Other code calls getData('author') and depends on the column nameOther code calls getAuthor() from the interface
A table change breaks other modulesThe table may change while the interface stays the same
The model cannot be exposed through the APIThe interface is connected to REST with one XML entry

Common mistake

The comments in a data interface are left out, or array and mixed are written in them. Everything works in PHP code, but on a REST call Magento cannot determine the field type and returns an error. State exact types: int, string, float, bool, an interface name, or string[] for a list.

Thanks for reading!

Donate to the site — we work hard for you.