Репозиторий (Repository Pattern) - шаблон проектирования в php.
  • 586

Шаблон проектирования Репозиторий (Repository Pattern).

Автор: admin | 16 июня (Вс.) 2019г. в 13ч.04м.

Для чего нужен паттерн Репозиторий.

Для простого объяснения назначения шаблона проектирования "Репозиторий" приведу пример из жизни. Допустим есть блог. Для работы с материалами блога нужен функционал CRUD. Допустим для хранения данных используется MySql. Где-то в контроллерах (сервисах) созданы методы для создания, обновления и удаления материалов с sql запросами к базе. А что если по какой-то причине понадобилось сменить MySql на MongoDB. Из этого следует, что код прийдется переписывать под MongoDB. Для того, чтобы код был более гибким, можно использовать  шаблон Repository, который будет посредником между хранилищем и нашим контроллером (или сервисом). Тогда можно при изменении типа хранилища просто использовать репозиторий и ничего переписыват не прийдется. Звучит не плохо, правда? А теперь разберем код.

Реализация Repository Pattern.

Контроллер для взаимодействия с хранилищем (базой данных) использует класс репозитория, который в свою очередь через классы адаптеры сохраняет\обновляет\находит\удаляет данные в самом хранилище.

На схеме шаблон проектирования "Репозиторий" будет выглядеть так:
UML диаграмма шаблона проектирования "Репозиторий"
Итак, создадим контроллер для работы с материалами блога. PostController:
<?php

namespace frontend\controllers;

use repository\domain\abstract\PostRepositoryInterface;

class PostController extends Controller
{
    private $repo;
    
    /**
    * В конструктор передаем класс репозитория, который реализует указанный интерфейс
    * Таким образом мы храним нужный репозиторий в свойстве $repo
    */
    public function __construct(PostRepositoryInterface $repo)
    {
        $this->repo = $repo;
    }

    /**
    * Работаем с данными и хранилищем через класс репозитория
    */
    public function actionIndex()
    {
        return $this->repo->all();
    }

    public function actionCreate($id)
    {
        return $this->repo->create($id);
    }

    public function actionUpdate($id)
    {
        return $this->repo->update($id);
    }

    public function actionDelete($id)
    {
        return $this->repo->delete($id);
    }

}​
Создадим интерфейс для репозиториев:
<?php

namespace repository\domain\abstract;

interface PostRepositoryInterface
{
    public function all();

    public function create($id);

    public function update($id);

    public function delete($id);

}​
Создадим класс репозитория, реализующий интерфейс PostRepositoryInterface:
<?php

namespace repository\domain;

use repository\domain\abstract\PostRepositoryInterface;
use storage\domain\abstract\StorageInterface;

class PostRepository implements PostRepositoryInterface
{
    private $storage;
    
    /**
    * В конструктор передаем класс хранилища, который реализует указанный интерфейс
    * Таким образом мы храним нужное хранилище в свойстве $storage
    */
    public function __construct(StorageInterface $storage)
    {
        $this->storage = $storage;
    }

    /**
    * Работаем с данными и хранилищем через класс репозитория
    */
    public function all()
    {
        return $this->storage->findAll('post');
    }

    public function actionCreate($data)
    {
        return $this->storage->create('post', $data);
    }

    public function actionUpdate($id, $data)
    {
        return $this->storage->update('post',$id, $data);
    }

    public function actionDelete($id)
    {
        return $this->repo->delete('post', $id);
    }

}​​
Обратите внимание, что в репозитории хранится класс для работы с хранилищем. Хранилище - это класс, в котором непосредственно реализован функционал для взаимодействием с базой данных или массивом или же другим типом хранилища. Все классы хранилища реализуют единий интерфейс StorageInterface. Поэтому, если мы заменим хранилище с MySql на MongoDB, то в репозитории ничего менять не нужно. Код становится гибче и соответствует принципам SOLID.

Теперь создадим интерфейс и несколько классов хранилищ.
<?php

namespace storage\domain\abstract;

interface StorageInterface
{
    public function findAll($part);

    public function create($part, $data);

    public function update($part, $id, $data);

    public function delete($part, $id);

}​​​
Теперь создадим два хранилища. Одно - для хранения в базе данных MySql, а другое для хранинеия в массиве (для примера):
<?php

namespace storage\domain\abstract;

use storage\domain\abstract\StorageInterface;
use storage\domain\connection\Connection;

class MySqlStorage implements StorageInterface
{
    public $connection;
    
    public function __construct()
    {
        $this->connection = Connection::getInstance();
    }

    public function findAll($part){
        $table = $part;
        return $this->connection->query('SELECT * FROM ' . $table);
    }

    public function create($part, $data){
         //Обработка создания записи в базе данных
    }

    public function update($part, $id, $data){
        //Обработка обновления записи в базе данных
    }

    public function delete($part, $id){
        //Обработка удаления записи в базе данных
    }

}​​​
Класс Connection, с помощью которого осуществляется подключение к базе данных. Кстати этот класс спроектирован по шаблону "Одиночка":
<?php

namespace storage\domain\connection;

class Connection
{
    protected static $instance;
    
    public static function getInstance() {
		if(empty(self::$instance)) {
			$db_info = array(
				"db_host" => "localhost",
				"db_port" => "3306",
				"db_user" => "user",
				"db_pass" => "pass",
				"db_name" => "ftonato",
				"db_charset" => "UTF-8");
			try {
				self::$instance = new PDO("mysql:host=".$db_info['db_host'].';port='.$db_info['db_port'].';dbname='.$db_info['db_name'], $db_info['db_user'], $db_info['db_pass']);
				self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);  
				self::$instance->query('SET NAMES utf8');
				self::$instance->query('SET CHARACTER SET utf8');
			} catch(PDOException $error) {
				echo $error->getMessage();
			}
		}
		return self::$instance;
	}

}​​​​

Для хранения в массиве:

<?php

namespace storage\domain\abstract;

use storage\domain\abstract\StorageInterface;

class ArrayStorage implements StorageInterface
{
    private $array = [];
    
    public function findAll($part){
        
        return $this->array[$part];
    }

    public function create($part, $data){
         //Обработка создания записи
         return $this->array[$part][] = $data;
    }

    public function update($part, $id, $data){
        //Обработка обновления записи
        return $this->array[$part][$id] = $data;
    }

    public function delete($part, $id){
        //Обработка удаления записи
        unset($this->array[$part][$id]);
        return true;
    }

}​​​​
Для простоты примера я сознательно не стал раздувать методы различными обработками и проверками, чтобы не запутать читателя и не увести внимание от сути.

Спасибо за внимание. Пользуйтесь шаблонами проектирования. Удачи!

Читайте также из этой серии:

Приветствую!

Меня зовут Сергей. Я - автор этого блога.

Если Вам был полезен материал на моем сайте, поддержите пожалуйста мой проект, чтобы о нем узнали другие люди - кликните plizz :) на иконку в соц. сети, чтобы поделиться материалом с другими.