テストを書くにあたり、より良い設計を考えて実装していくのはフレームワークの機能ではなく、
開発している方次第です。
Eloquentに依存しているシステムなども今一度考えながらリファクタリングを目指してみましょう。
ということで、今回は巷で言われているリポジトリーパターン風ではなく、
スタンダードなリポジトリ+エンティティをLaravelのデータベースコンポーネントの
クエリービルダーを使って実装するサンプルです。
まず一つ、コントローラにEloquentなどのデータベースを用いる処理を乗せればMVCじゃん!
という意識を少し変える必要があります。
今回の流れは リポジトリ->エンティティ->サービス->コントローラ として実装します。
モデルという言葉はどこにも出てきませんが、
この流れを理解するにはモデル=なんとかテーブルという意識を変える必要があり、
データ層を司るものがモデルで、EloquentはたまたまORMなのでデータベースに依存するデータ層を操作するもの、
として捉えます。
= リポジトリはデータ層を操作するものですので、
それに利用するものはデータベースやNoSQL、何かのオブジェクトやなんでも、それに依存しない層です。
データベースは前回のものと同じく
public function up()
{
Schema::create($this->table, function (Blueprint $table) {
$table->increments('entry_id');
$table->string('title')->index('entry_title');
$table->longText('content');
$table->timestamps();
});
}
です。
namespace App\Entities;
/**
* Class EntryEntity
* @package App\Entities
*/
class EntryEntity implements Entityable
{
/** @var int */
private $entry_id;
/** @var string */
public $title;
/** @var string */
public $content;
/** @var string */
public $created_at;
/** @var string */
public $updated_at;
/**
* @return int
*/
public function getId()
{
return $this->entry_id;
}
}
ただのクラスですね
namespace App\Repositories;
use App\Entities\EntryEntity;
/**
* Interface EntryRepository
* @package App\Repositories
*/
interface EntryRepositoryInterface
{
/**
* @param EntryEntity $item
* @return EntryEntity
*/
public function save(EntryEntity $item);
/**
* @param $id
* @return EntryEntity|null
*/
public function find($id);
/**
* @return mixed
*/
public function findAll();
}
↑のようなインターフェースを用意します。
namespace App\Repositories;
use App\Entities\EntryEntity;
use App\Repositories\Criteria\Entryable;
/**
* Class EntryRepository
* @package App\Repositories
*/
class EntryRepository implements EntryRepositoryInterface
{
/** @var Entryable */
protected $entryable;
/**
* @param Entryable $entryable
*/
public function __construct(Entryable $entryable)
{
$this->entryable = $entryable;
}
/**
* @param EntryEntity $item
* @return EntryEntity
*/
public function save(EntryEntity $item)
{
$this->entryable->save($item);
return $item;
}
/**
* @param $id
* @return EntryEntity|null
*/
public function find($id)
{
return $this->entryable->find($id);
}
/**
* @return mixed
*/
public function findAll()
{
return $this->entryable->findAll();
}
}
エンティティを利用してsaveするとなります。
namespace App\Repositories\Criteria;
use App\Entities\EntryEntity;
/**
* Interface Entryable
* @package App\Repositories\Criteria
*/
interface Entryable
{
/**
* @param EntryEntity $entity
* @return mixed
*/
public function save(EntryEntity $entity);
/**
* @param $id
* @return mixed
*/
public function find($id);
/**
* @return mixed
*/
public function findAll();
}
インターフェースの名前があまりよくないですが、Entry関連を操作するもの、ということです。
namespace App\Repositories\Criteria;
use App\Entities\EntryEntity;
use Illuminate\Database\DatabaseManager;
/**
* Class EntryDataAccessObject
* @package App\Repositories\Criteria
*/
class EntryDataAccessObject extends FluentObject implements Entryable
{
/** @var DatabaseManager */
protected $db;
/** @var string */
protected $table = 'entries';
/** @var string */
protected $identity = 'entry_id';
/**
* @param DatabaseManager $db
*/
public function __construct(DatabaseManager $db)
{
$this->db = $db;
}
/**
* @param EntryEntity $item
* @return int
*/
public function save(EntryEntity $item)
{
if (is_null($item->getId())) {
return $this->db->connection()
->table($this->table)->insertGetId($this->data($item));
}
return $this->db->connection()
->table($this->table)
->where('entry_id', $item->getId())->update($this->data($item));
}
/**
* @param $id
* @return EntryEntity
*/
public function find($id)
{
$data = $this->db->connection()
->table($this->table)->where('entry_id', $id)->first();
if(is_null($data)) {
return null;
}
return $this->getData($data, new EntryEntity);
}
/**
* @return array|static[]
*/
public function findAll()
{
return $this->db->connection()
->table($this->table)->get();
}
}
では全体ができたところで利用のイメージですが、
namespace App\Services;
use Carbon\Carbon;
use App\Entities\EntryEntity;
use App\Repositories\EntryRepositoryInterface;
/**
* Class EntryService
* @package App\Services
*/
class EntryService
{
/** @var EntryRepositoryInterface */
protected $entry;
/**
* @param EntryRepositoryInterface $entry
*/
public function __construct(EntryRepositoryInterface $entry)
{
$this->entry = $entry;
}
/**
* @param $id
* @return \App\Entities\EntryEntity|null
*/
public function getEntry($id)
{
return $this->entry->find($id);
}
/**
* @param array $params
* @return EntryEntity
*/
public function setEntry(array $params)
{
$entry = new EntryEntity;
$entry->content = $params['content'];
$entry->title = $params['title'];
$datetime = Carbon::now()->toDateTimeString();
$entry->created_at = $datetime;
$entry->updated_at = $datetime;
return $this->entry->save($entry);
}
}
リポジトリのインターフェースをタイプヒンティングで指定し、
$this->app->bind(
'App\Repositories\EntryRepositoryInterface',
'App\Repositories\EntryRepository'
);
$this->app->bind(
'App\Repositories\Criteria\Entryable',
'App\Repositories\Criteria\EntryDataAccessObject'
);
あとはビジネスロジックを利用するコントローラで
/** @var EntryService */
protected $entry;
/**
* @param EntryService $entry
*/
public function __construct(EntryService $entry)
{
$this->entry = $entry;
}
/**
* @param $id
* @return \App\Entities\EntryEntity|null
*/
public function show($id = null)
{
return response()->json($this->entry->getEntry($id));
}
/**
* @return \App\Entities\EntryEntity
*/
public function store()
{
$this->entry->setEntry([
'title' => 'hello Laravel5.1',
'content' => 'Laravel makes connecting with databases and running queries extremely simple.',
]);
}
のように利用するだけです。laravel 42
DTM 0
music 0
PHP全般 27
食 0
JAPAN 1
WORLD 1
RDBMS 1
NoSQL 1
NewSQL 1