A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

4.20. Tipos de Modelo

En Zend Framework 3, como ya pudimos adivinar, no hay una sola carpeta Model para guardar las clases de modelos. En cambio y por convención, los modelos son subdivididos en los siguientes tipos principales que son guardados cada uno en sus propias subcarpetas (ver tabla 4.9):

Table 4.9. Tipos de Modelos y su ubicación
Tipo de Modelo Carpeta
Entidades APP_DIR/module/Application/src/Entity
Repositorios APP_DIR/module/Application/src/Repository
Objetos con Valor (Value Objects) APP_DIR/module/Application/src/ValueObject
Servicios APP_DIR/module/Application/src/Service
Fábricas Una subcarpeta Factory dentro de cada carpeta de tipo de modelo. Por ejemplo, las fábricas para los controladores se almecenarán en APP_DIR/module/Application/src/Controller/Factory

Separar los modelos en diferentes tipos hace más fácil el diseño de nuestro dominio de reglas de negocio. Esto es también llamado "Diseño guiado por dominio" (o brevemente DDD). La persona que propuso DDD es Eric Evans en su famoso libro llamado Domain-Driven Design — Tackling Complexity in the Heart of Software.

Abajo describiremos los principales tipos de modelos.

4.20.1. Entidades

Las Entidades se usan para guardar datos que siempre tienen una propiedad que funciona como identificador de manera que podemos identificar de manera única los datos. Por ejemplo, una entidad User siempre tiene la propiedad única login, y mediante este atributo podemos identificar al usuario. Podemos cambiar los otros atributos de la entidad, como primerNombre o dirección pero su identificador nunca cambia. Las entidades son almacenadas usualmente en una base de datos, en un archivo del sistema o en cualquier otro almacenamiento.

Abajo podemos encontrar un ejemplo de la entidad User que representa a los usuarios del sitio web:

// 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 function setLogin($login)
    {
        $this->login = $login;
    }

    //...
}

En las líneas 5-12 definimos las propiedades del modelo User. La mejor práctica es definir las propiedades usando el tipo de acceso privado y hacerlas disponibles a través de los métodos públicos getter y setter (como getLogin() y setLogin(), etc).

El comportamiento de los métodos del modelo no se limitan a los getters y setters. Podemos crear otros métodos que manipulan los datos del modelo. Por ejemplo, podemos definir a conveniencia el método getFullName() que regresaría el nombre completo del usuario: "Mr. John Doe".

4.20.2. Repositorios

Los repositorios son modelos específicos responsables de guardar y recuperar entidades. Por ejemplo, el UserRepository puede representar una tabla de la base de datos y proveer los métodos para recuperar las entidades User. Generalmente usamos los repositorios cuando guardamos entidades en la base de datos. Con los repositorios podemos encapsular la lógica de la consulta SQL en un solo lugar, y de fácil mantenimiento y, además, probarlos.

Aprenderemos sobre los repositorios con más detalles en Administración de la Base de Datos con Doctrine en donde hablaremos sobre la biblioteca Doctrine.

4.20.3. Objetos con Valor

Los objetos con valor son un tipo de modelo en que no es importante la identidad como si lo es en las entidades. Un objeto con valor es usualmente una pequeña clase identificada por medio de todos su atributos. Él no tiene un atributo identificador. Los objetos con valor típicamente tienen métodos getter pero no tienen métodos setters (los objetos con valor son inmutables).

Por ejemplo, un modelo que maneja una cantidad de dinero puede ser tratado como un objeto con valor:

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;
    }
}

En las líneas 4-5 definimos dos propiedades: currency y amount. El modelo no tiene una propiedad como identificador único, en cambio su identidad se define por medio de todas sus propiedades: si cambiamos la propiedad currency o amount tendríamos diferentes objetos con diferentes cantidades de dinero.

En las líneas 8-12 definimos el método constructor que inicializa las propiedades.

En las líneas 15-24 definimos los métodos getter para las propiedades del modelo. Veamos que no tenemos métodos setter (el modelo es inmutable).

4.20.4. Servicios

Los modelos de tipo servicio usualmente encapsulan alguna parte de las funcionalidades de la lógica de negocio. Los servicios tienen nombres reconocibles fácilmente por su terminación en "er", como FileUploader o UserManager.

Abajo un ejemplo del servicio Mailer se presenta. Este tiene el método sendMail() que toma el objeto EmailMessage como valor y enviá un mensaje de correo electrónico usando la función estándar mail() de PHP:

<?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;
    }
}

En Zend Framework registramos nuestros modelos de tipo servicio en el Administrador de Servicios.

4.20.5. Fábricas

Las fábricas se diseñan usualmente para instanciar otros modelos (particularmente los modelos de tipo servicio). En los casos más simples podemos crear una instancia sin una fábrica mediante el uso del operador new pero a veces la lógica de creación de clases debe ser más compleja. Por ejemplo, es común que los servicios dependan de otros servicios, así necesitaremos inyectar dependencias a un servicio. Además, puede ser necesario inicializar el servicio justo después de la instanciación mediante el llamado de uno o varios de sus métodos.

Los nombres de las clases tienen típicamente nombres que terminan con el sufijo Factory tal como CurrencyConverterFactory, MailerFactory, etc.

Como un ejemplo de la vida real vamos a imaginar que tenemos el servicio PurchaseManager, que puede procesar las compras de determinados bienes y que el servicio PurchaseManager usa otro servicio llamado CurrencyConverter que se puede conectar a un servicio externo que provee las tasas de cambio. Vamos a escribir una clase de tipo fábrica para el PurchaseManager que instanciará el servicio y lo pasará como dependencia:

<?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);
    }
}

En el código de arriba tenemos la clase PurchaseManagerFactory que implementa la interface Zend\ServiceManager\Factory\FactoryInterface. La clase de tipo fábrica tiene el método __invoke() cuyo objetivo es instanciar el objeto. Este método tiene el argumento $container que es el administrador de servicios. Podemos usar la variable $container para recuperar servicios del administrador de servicios y pasarlos al método constructor del servicio que se está instanciando.


Top