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 changedThe 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 contract | With a contract |
|---|---|
Other code calls getData('author') and depends on the column name | Other code calls getAuthor() from the interface |
| A table change breaks other modules | The table may change while the interface stays the same |
| The model cannot be exposed through the API | The 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.