这是域映射器模型的通用结构吗? [英] Is this the common structure for the domain mapper model?

查看:55
本文介绍了这是域映射器模型的通用结构吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

希望我在正确的堆栈交换论坛上提出这个问题.如果没有,请告诉我,我会在其他地方问.我也曾问过Code Review,但社区似乎没有那么活跃.

由于我已经自学了PHP和一般的所有程序,所以直到最近才发现有关数据映射器"的信息,该数据映射器允许将数据传递到类中,而使所述类不知道数据来自何处.我已经读到了使用映射器的一些优点,以及为什么它们使在以后进行升级时更轻松"的原因,但是我真的很难找出在目录结构中使用映射器及其布局的推荐方法.

假设我们有一个简单的应用程序,其目的是回显用户的名字和姓氏.

我一直使用/创建映射器的方式(以及文件结构如下):

index.php

include 'classes/usermapper.php';
include 'classes/user.php';

$user = new User;
$userMapper = new userMapper;

try {
  $user->setData([
    $userMapper->fetchData([
      'username'=>'peter1'
    ])
  ]);
} catch (Exception $e) {
  die('Error occurred');
}

if ($user->hasData()) {
  echo $user->fullName();
}

classes/user.php

class User {
  private $_data;

  public function __construct() { }

  public function setData($userObject = null) {
    if (!$userObject) { throw new InvalidArgumentException('No Data Set'); }
    $this->_data = $dataObject;
  }

  public function hasData() {
    return (!$this->_data) ? false : true;
  }

  public function fullName() {
    return ucwords($this->_data->firstname.' '.$this->_data->lastname);
  }
}

classes/usermapper.php

class userMapper {
  private $_db;

  public function __construct() { $this->_db = DB::getInstance(); }

  public function fetchData($where = null) {
    if (!is_array($where)) { 
      throw new InvalidArgumentException('Invalid Params Supplied'); 
    }

    $toFill = null;
    foreach($where as $argument=>$value) {
      $toFill .= $argument.' = '.$value AND ;
    }

    $query = sprintf("SELECT * FROM `users` WHERE %s ", substr(rtrim($toFill), 0, -3));


    $result = $this->_db->query($query); //assume this is just a call to a database which returns the results of the query

    return $result;
  }
}

了解到用户表包含用户名,名字和姓氏,并且还缺少许多清理检查的知识,为什么映射器易于使用?

这是一种获取数据的漫长方法,并且假设用户不是全部,而是订单,付款,票证,公司等等都具有对应的映射器,因此,不创建一个映射器似乎是一种浪费.并在每个班级的任何地方实施它. 这使文件夹结构看起来更好,也意味着代码不会重复出现.

在每种情况下,从中提取数据的表中,示例映射器的外观都相同.

因此,我的问题是.这是域模型映射器"下的数据映射器的外观吗?如果没有,我的代码将如何改进?其次,在所有需要从数据库中提取数据的情况下,无论类的大小如何,都需要该模型,或者在这种情况下,仅在 user.php 类很大的情况下使用该模型?

在此先感谢您的帮助.

解决方案

数据映射器将域对象与持久性存储(数据库)完全分开,并提供特定于域级操作的方法.使用它可以将数据从域传输到数据库,反之亦然.在一种方法中,通常会执行数据库查询,然后将结果映射(水合)到域对象或域对象列表.

示例:

基类:Mapper.php

abstract class Mapper
{
    protected $db;

    public function __construct(PDO $db)
    {
        $this->db = $db;
    }
}

文件:BookMapper.php

class BookMapper extends Mapper
{
    public function findAll(): array
    {
        $sql = "SELECT id, title, price, book_category_id FROM books;";
        $statement = $this->db->query($sql);

        $items = [];
        while ($row = $statement->fetch()) {
            $items[] = new BookEntity($row);
        }

        return $items;
    }

    public function findByBookCategoryId(int $bookCategoryId): array
    {
        $sql = "SELECT id, title, price, book_category_id 
                FROM books
                WHERE book_category_id = :book_category_id;";

        $statement = $this->db->prepare($sql);
        $statement->execute(["book_category_id" => $bookCategoryId]);

        $items = [];
        while ($row = $statement->fetch()) {
            $items[] = new BookEntity($row);
        }

        return $items;
    }

    /**
     * Get one Book by its ID
     *
     * @param int $bookId The ID of the book
     * @return BookEntity The book
     * @throws RuntimeException
     */
    public function getById(int $bookId): BookEntity
    {
        $sql = "SELECT id, title, price, book_category_id FROM books 
                WHERE id = :id;";

        $statement = $this->db->prepare($sql);

        if (!$result = $statement->execute(["id" => $bookId])) {
            throw new DomainException(sprintf('Book-ID not found: %s', $bookId));
        }

        return new BookEntity($statement->fetch());
    }

    public function insert(BookEntity $book): int
    {
        $sql = "INSERT INTO books SET title=:title, price=:price, book_category_id=:book_category_id";
        $statement = $this->db->prepare($sql);

        $result = $statement->execute([
            'title' => $book->getTitle(),
            'price' => $book->getPrice(),
            'book_category_id' => $book->getBookCategoryId(),
        ]);

        if (!$result) {
            throw new RuntimeException('Could not save record');
        }

        return (int)$this->db->lastInsertId();
    }
}

文件:BookEntity.php

class BookEntity
{
    /** @var int|null */
    protected $id;

    /** @var string|null */
    protected $title;

    /** @var float|null */
    protected $price;

    /** @var int|null */
    protected $bookCategoryId;

    /**
     * Accept an array of data matching properties of this class
     * and create the class
     *
     * @param array|null $data The data to use to create
     */
    public function __construct(array $data = null)
    {
        // Hydration (manually)
        if (isset($data['id'])) {
            $this->setId($data['id']);
        }
        if (isset($data['title'])) {
            $this->setTitle($data['title']);
        }
        if (isset($data['price'])) {
            $this->setPrice($data['price']);
        }
        if (isset($data['book_category_id'])) {
            $this->setBookCategoryId($data['book_category_id']);
        }
    }

    /**
     * Get Id.
     *
     * @return int|null
     */
    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * Set Id.
     *
     * @param int|null $id
     * @return void
     */
    public function setId(?int $id): void
    {
        $this->id = $id;
    }

    /**
     * Get Title.
     *
     * @return null|string
     */
    public function getTitle(): ?string
    {
        return $this->title;
    }

    /**
     * Set Title.
     *
     * @param null|string $title
     * @return void
     */
    public function setTitle(?string $title): void
    {
        $this->title = $title;
    }

    /**
     * Get Price.
     *
     * @return float|null
     */
    public function getPrice(): ?float
    {
        return $this->price;
    }

    /**
     * Set Price.
     *
     * @param float|null $price
     * @return void
     */
    public function setPrice(?float $price): void
    {
        $this->price = $price;
    }

    /**
     * Get BookCategoryId.
     *
     * @return int|null
     */
    public function getBookCategoryId(): ?int
    {
        return $this->bookCategoryId;
    }

    /**
     * Set BookCategoryId.
     *
     * @param int|null $bookCategoryId
     * @return void
     */
    public function setBookCategoryId(?int $bookCategoryId): void
    {
        $this->bookCategoryId = $bookCategoryId;
    }
}

文件:BookCategoryEntity.php

class BookCategoryEntity
{
    const FANTASY = 1;
    const ADVENTURE = 2;
    const COMEDY = 3;

    // here you can add the setter and getter methods
}

表结构:schema.sql

CREATE TABLE `books` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `price` decimal(19,2) DEFAULT NULL,
  `book_category_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `book_category_id` (`book_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `book_categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

/*Data for the table `book_categories` */

insert  into `book_categories`(`id`,`title`) values (1,'Fantasy');
insert  into `book_categories`(`id`,`title`) values (2,'Adventure');
insert  into `book_categories`(`id`,`title`) values (3,'Comedy');

用法

// Create the database connection
$host = '127.0.0.1';
$dbname = 'test';
$username = 'root';
$password = '';
$charset = 'utf8';
$collate = 'utf8_unicode_ci';
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_PERSISTENT => false,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset COLLATE $collate"
];

$db = new PDO($dsn, $username, $password, $options);

// Create the data mapper instance
$bookMapper = new BookMapper($db);

// Create a new book entity
$book = new BookEntity();
$book->setTitle('Harry Potter');
$book->setPrice(29.99);
$book->setBookCategoryId(BookCategoryEntity::FANTASY);

// Insert the book entity
$bookId = $bookMapper->insert($book);

// Get the saved book
$newBook = $bookMapper->getById($bookId);
var_dump($newBook);

// Find all fantasy books
$fantasyBooks = $bookMapper->findByBookCategoryId(BookCategoryEntity::FANTASY);
var_dump($fantasyBooks);

Hopefully i am asking this on the right stack exchange forum. If not please do let me know and I will ask somewhere else. I have also asked on Code Review, but the community seems a lot less active.

As I have self learned PHP and all programming in general, I have only recently found out about 'Data Mappers' which allows data to be passed into classes without said classes knowing where the data comes from. I have read some of the positives of using mappers and why they make it 'easier' to perform upgrades later down the line, however I am really struggling to find out the reccomended way of using mappers and their layouts in a directory structure.

Let's assume we have a simple application whos purpose is to echo out a first name and last name of a user.

The way I have been using/creating mappers (as well as the file structure is as follows):

index.php

include 'classes/usermapper.php';
include 'classes/user.php';

$user = new User;
$userMapper = new userMapper;

try {
  $user->setData([
    $userMapper->fetchData([
      'username'=>'peter1'
    ])
  ]);
} catch (Exception $e) {
  die('Error occurred');
}

if ($user->hasData()) {
  echo $user->fullName();
}

classes/user.php

class User {
  private $_data;

  public function __construct() { }

  public function setData($userObject = null) {
    if (!$userObject) { throw new InvalidArgumentException('No Data Set'); }
    $this->_data = $dataObject;
  }

  public function hasData() {
    return (!$this->_data) ? false : true;
  }

  public function fullName() {
    return ucwords($this->_data->firstname.' '.$this->_data->lastname);
  }
}

classes/usermapper.php

class userMapper {
  private $_db;

  public function __construct() { $this->_db = DB::getInstance(); }

  public function fetchData($where = null) {
    if (!is_array($where)) { 
      throw new InvalidArgumentException('Invalid Params Supplied'); 
    }

    $toFill = null;
    foreach($where as $argument=>$value) {
      $toFill .= $argument.' = '.$value AND ;
    }

    $query = sprintf("SELECT * FROM `users` WHERE %s ", substr(rtrim($toFill), 0, -3));


    $result = $this->_db->query($query); //assume this is just a call to a database which returns the results of the query

    return $result;
  }
}

With understanding that the users table contains a username, first name and last name, and also that a lot of sanitizing checks are missing, why are mappers convenient to use?

This is a very long winded way in getting data, and assuming that users aren't everything, but instead orders, payments, tickets, companies and more all have their corresponding mappers, it seems a waste not to create just one mapper and implement it everywhere in each class. This allows the folder structure to look a whole lot nicer and also means that code isn't repeated as often.

The example mappers looks the same in every case bar the table the data is being pulled from.

Therefore my question is. Is this how data mappers under the 'domain model mappers' should look like, and if not how could my code be improved? Secondly is this model needed in all cases of needing to pull data from a database, regardless of the size of class, or should this model only be used where the user.php class in this case is very large?

Thank you in advance for all help.

解决方案

The Data Mapper completely separates the domain objects from the persistent storage (database) and provides methods that are specific to domain-level operations. Use it to transfer data from the domain to the database and vice versa. Within a method, a database query is usually executed and the result is then mapped (hydrated) to a domain object or a list of domain objects.

Example:

The base class: Mapper.php

abstract class Mapper
{
    protected $db;

    public function __construct(PDO $db)
    {
        $this->db = $db;
    }
}

The file: BookMapper.php

class BookMapper extends Mapper
{
    public function findAll(): array
    {
        $sql = "SELECT id, title, price, book_category_id FROM books;";
        $statement = $this->db->query($sql);

        $items = [];
        while ($row = $statement->fetch()) {
            $items[] = new BookEntity($row);
        }

        return $items;
    }

    public function findByBookCategoryId(int $bookCategoryId): array
    {
        $sql = "SELECT id, title, price, book_category_id 
                FROM books
                WHERE book_category_id = :book_category_id;";

        $statement = $this->db->prepare($sql);
        $statement->execute(["book_category_id" => $bookCategoryId]);

        $items = [];
        while ($row = $statement->fetch()) {
            $items[] = new BookEntity($row);
        }

        return $items;
    }

    /**
     * Get one Book by its ID
     *
     * @param int $bookId The ID of the book
     * @return BookEntity The book
     * @throws RuntimeException
     */
    public function getById(int $bookId): BookEntity
    {
        $sql = "SELECT id, title, price, book_category_id FROM books 
                WHERE id = :id;";

        $statement = $this->db->prepare($sql);

        if (!$result = $statement->execute(["id" => $bookId])) {
            throw new DomainException(sprintf('Book-ID not found: %s', $bookId));
        }

        return new BookEntity($statement->fetch());
    }

    public function insert(BookEntity $book): int
    {
        $sql = "INSERT INTO books SET title=:title, price=:price, book_category_id=:book_category_id";
        $statement = $this->db->prepare($sql);

        $result = $statement->execute([
            'title' => $book->getTitle(),
            'price' => $book->getPrice(),
            'book_category_id' => $book->getBookCategoryId(),
        ]);

        if (!$result) {
            throw new RuntimeException('Could not save record');
        }

        return (int)$this->db->lastInsertId();
    }
}

The file: BookEntity.php

class BookEntity
{
    /** @var int|null */
    protected $id;

    /** @var string|null */
    protected $title;

    /** @var float|null */
    protected $price;

    /** @var int|null */
    protected $bookCategoryId;

    /**
     * Accept an array of data matching properties of this class
     * and create the class
     *
     * @param array|null $data The data to use to create
     */
    public function __construct(array $data = null)
    {
        // Hydration (manually)
        if (isset($data['id'])) {
            $this->setId($data['id']);
        }
        if (isset($data['title'])) {
            $this->setTitle($data['title']);
        }
        if (isset($data['price'])) {
            $this->setPrice($data['price']);
        }
        if (isset($data['book_category_id'])) {
            $this->setBookCategoryId($data['book_category_id']);
        }
    }

    /**
     * Get Id.
     *
     * @return int|null
     */
    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * Set Id.
     *
     * @param int|null $id
     * @return void
     */
    public function setId(?int $id): void
    {
        $this->id = $id;
    }

    /**
     * Get Title.
     *
     * @return null|string
     */
    public function getTitle(): ?string
    {
        return $this->title;
    }

    /**
     * Set Title.
     *
     * @param null|string $title
     * @return void
     */
    public function setTitle(?string $title): void
    {
        $this->title = $title;
    }

    /**
     * Get Price.
     *
     * @return float|null
     */
    public function getPrice(): ?float
    {
        return $this->price;
    }

    /**
     * Set Price.
     *
     * @param float|null $price
     * @return void
     */
    public function setPrice(?float $price): void
    {
        $this->price = $price;
    }

    /**
     * Get BookCategoryId.
     *
     * @return int|null
     */
    public function getBookCategoryId(): ?int
    {
        return $this->bookCategoryId;
    }

    /**
     * Set BookCategoryId.
     *
     * @param int|null $bookCategoryId
     * @return void
     */
    public function setBookCategoryId(?int $bookCategoryId): void
    {
        $this->bookCategoryId = $bookCategoryId;
    }
}

The file: BookCategoryEntity.php

class BookCategoryEntity
{
    const FANTASY = 1;
    const ADVENTURE = 2;
    const COMEDY = 3;

    // here you can add the setter and getter methods
}

The table structure: schema.sql

CREATE TABLE `books` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `price` decimal(19,2) DEFAULT NULL,
  `book_category_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `book_category_id` (`book_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `book_categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

/*Data for the table `book_categories` */

insert  into `book_categories`(`id`,`title`) values (1,'Fantasy');
insert  into `book_categories`(`id`,`title`) values (2,'Adventure');
insert  into `book_categories`(`id`,`title`) values (3,'Comedy');

Usage

// Create the database connection
$host = '127.0.0.1';
$dbname = 'test';
$username = 'root';
$password = '';
$charset = 'utf8';
$collate = 'utf8_unicode_ci';
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_PERSISTENT => false,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset COLLATE $collate"
];

$db = new PDO($dsn, $username, $password, $options);

// Create the data mapper instance
$bookMapper = new BookMapper($db);

// Create a new book entity
$book = new BookEntity();
$book->setTitle('Harry Potter');
$book->setPrice(29.99);
$book->setBookCategoryId(BookCategoryEntity::FANTASY);

// Insert the book entity
$bookId = $bookMapper->insert($book);

// Get the saved book
$newBook = $bookMapper->getById($bookId);
var_dump($newBook);

// Find all fantasy books
$fantasyBooks = $bookMapper->findByBookCategoryId(BookCategoryEntity::FANTASY);
var_dump($fantasyBooks);

这篇关于这是域映射器模型的通用结构吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆