In Zend Framework 3, there is no single Model
directory for storing the model classes, as you could
assume. Instead, by convention, models are further subdivided into the following principal types, and each type is
stored in its own subdirectory (see table 4.9):
Model Type | Directory |
---|---|
Entities | APP_DIR/module/Application/src/Entity |
Repositories | APP_DIR/module/Application/src/Repository |
Value Objects | APP_DIR/module/Application/src/ValueObject |
Services | APP_DIR/module/Application/src/Service |
Factories | In Factory subdirectory under each model type directory. For example, controller factories would be stored in APP_DIR/module/Application/src/Controller/Factory |
Separation of models into different types make it easier to design your business logic domain. This is also called the "Domain Driven Design" (or shortly, DDD). The person who proposed DDD was Eric Evans in his famous book called Domain-Driven Design — Tackling Complexity in the Heart of Software.
Below, we will describe the principal model types further.
Entities are intended for storing some data and always have some identifier property, so you can uniquely identify the data.
For example, a User
entity always has a unique login
property,
and you can identify the user by that attribute. You can change some other attributes
of the entity, like firstName
, or address
, but its identifier never changes.
Entities are usually stored in a database, in a file system or in any other storage.
Below, you can find an example a User
entity, which represents a site visitor:
// The User entity represents a site visitor
class User
{
// Properties
private $login; // e.g. "admin"
private $title; // e.g. "Mr."
private $firstName; // e.g. "John"
private $lastName; // e.g. "Doe"
private $country; // e.g. "USA"
private $city; // e.g. "Paris"
private $postCode; // e.g. "10543"
private $address; // e.g. "Jackson rd."
// Behaviors
public function getLogin()
{
return $this->login;
}
public setLogin($login)
{
$this->login = $login;
}
//...
}
In lines 5-12, we define User
model's properties. The best practice is to
define the properties using the private access type, and make
them available to the caller through getter and setter public methods
(like getLogin()
and setLogin()
, etc).
Model's behavior methods are not limited by getters and setters. You can create other methods which manipulate model's data. For example, you can define the
getFullName()
convenience method, which would return the user's full name, like "Mr. John Doe".
Repositories are specific models responsible for storing and retrieving entities.
For example, a UserRepository
may represent a database table and provide methods
for retrieving User
entities. You typically use repositories when storing entities
in a database. With repositories, you can encapsulate SQL query logic in the
single place and easily maintain and test it.
We will learn about repositories in more details in Database Management with Doctrine, when talking about Doctrine library.
Value objects are a kind of model for which the identity is not as important as for entities. A value object is usually a small class identified by all of its attributes. It does not have an identifier attribute. Value objects typically have getter methods, but do not have setters (value objects are immutable).
For example, a model wrapping a money amount can be treated as a value object:
class MoneyAmount
{
// Properties
private $currency;
private $amount;
// Constructor
public function __construct($amount, $currency='USD')
{
$this->amount = $amount;
$this->currency = $currency;
}
// Gets the currency code
public function getCurrency()
{
return $this->currency;
}
// Gets the money amount
public function getAmount()
{
return $this->amount;
}
}
In lines 4-5 we define two properties: currency
and amount
. The model
has no identifier property, instead it is identified by all properties as
a whole: if you change either the currency
or amount
, you would have a
different money amount object.
In lines 8-12 we define the constructor method, which initializes the properties.
In lines 15-24, we define getter methods for model's properties. Note that we do not have setter methods (the model is immutable).
Service models usually encapsulate some business logic functionality.
Services usually have easily recognizable names ending with "er" suffix, like FileUploader
or UserManager
.
Below, an example of Mailer
service is presented. It has the sendMail()
method which takes an EmailMessage
value object and sends an E-mail message
using standard PHP mail()
function:
<?php
// The Email message value object
class EmailMessage
{
private $recipient;
private $subject;
private $text;
// Constructor
public function __construct($recipient, $subject, $text)
{
$this->recipient = $recipient;
$this->subject = $subject;
$this->text = $text;
}
// Getters
public function getRecipient()
{
return $this->recipient;
}
public function getSubject()
{
return $this->subject;
}
public function getText()
{
return $this->text;
}
}
// The Mailer service, which can send messages by E-mail
class Mailer
{
public function sendMail($message)
{
// Use PHP mail() function to send an E-mail
if(!mail($message->getRecipient(), $message->getSubject(),
$message()->getText()))
{
// Error sending message
return false;
}
return true;
}
}
In Zend Framework, you typically register your service models in Service Manager.
Factories are usually being designed to instantiate other models (particularly, service models). In the simplest cases you
can create an instance of a service without any factory, just by using the new
operator, but
sometimes class creation logic might be rather complex. For example, services often depend on each other,
so you might need to inject dependencies to a service. Also, sometimes it may be required to initialize the service
right after instantiation by calling one (or several) of its methods.
Factory classes typically have names ending with Factory
suffix, like
CurrencyConverterFactory
, MailerFactory
, etc.
For a real-life example, let's imagine that we have a PurchaseManager
service, which can process purchases of some goods,
and that the PurchaseManager
service uses another service named CurrencyConverter
, which can connect to an external
system providing money exchange rates. Let's write a factory class for the PurchaseManager
, which would instantiate
the service and pass it the dependency:
<?php
namespace Application\Service\Factory;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\CurrencyConverter;
use Application\Service\PurchaseManager;
/**
* This is the factory for PurchaseManager service. Its purpose is to instantiate the
* service and inject its dependencies.
*/
class PurchaseManagerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container,
$requestedName, array $options = null)
{
// Get CurrencyConverter service from the service manager.
$currencyConverter = $container->get(CurrencyConverter::class);
// Instantiate the service and inject dependencies.
return new PurchaseManager($currencyConverter);
}
}
In the code above we have the PurchaseManagerFactory
class which implements the
Zend\ServiceManager\Factory\FactoryInterface
interface. The factory class has the __invoke()
method
whose goal is to instantiate the object. This method has the $container
argument which is the service
manager. You can use $container
to retrieve services from service manager and pass them to the constructor
method of the service being instantiated.