リポジトリパターンを使用してPHPアプリケーションを構築しようとしていますが、save
メソッドを実装する方法がわかりません。
次のメソッドを持つItemRepository
という抽象クラスがあります。
abstract class ItemRepository
{
public function save(Item $item);
}
ここで、Item
も抽象クラスです。
次に、MovieRepository
を拡張するクラスItemRepository
を実装したいと思います。ここで、Movie
を拡張するItem
クラスのインスタンスを保存します。
PHPでこのようにしていても
// MovieRepository.php
class MovieRepository extends ItemRepository
{
public function save(Movie $movie)
{
...
}
}
次のエラーを出します
MovieRepository :: save()の宣言は、ItemRepository :: save(Item $ item)と互換性がある必要があります
これを行う正しい方法は何ですか?
Save /)メソッドからアイテム/映画のタイプをドロップします。
abstract class ItemRepository
{
public abstract function save($item);
}
class MovieRepository extends ItemRepository
{
public function save($movie)
{
...
}
}
PHPは主に動的に型付けされた言語であり、型をできるだけ使用したいという欲求は理解していますが、この場合、PHPの型システムは必要なものを表現するのに十分強力ではありません。他の言語(Java、C++)では、PHPにないジェネリックで表現できます。
私が推測しているのは、あなたがやろうとしていることは、save()
の一般的な実装があることですが、各リポジトリー・タイプの正しいタイプ・チェックで公開されていますか? (abstract
でsave()
をItemRepository
として宣言しなかったので、そこにメソッド本体があると思いますか?)
異なるリポジトリタイプのsave()
の引数タイプは異なるため、同じsave()
メソッドについては説明しません。つまり、メソッドにsave()
各リポジトリー・タイプでの規則になりますが、たとえば、 Movie
はItem
であるため、save(Movie $movie)
はsave(Item $item)
と互換性がありませんが、Item
は必ずしもMovie
。
これに基づいて、内部の保存メソッドを抽象基本クラスに抽出することをお勧めします。
abstract class AbstractItemRepository {
protected function saveItem(Item $item) {
// ...
}
}
class ItemRepository extends AbstractItemRepository {
public function save(Item $item) {
$this->saveItem($item);
}
}
class MovieRepository extends AbstractItemRepository {
public function save(Movie $movie) {
$this->saveItem($movie);
}
}
つまり、抽象基本クラスに共有機能を配置しますが、パブリックインターフェイスの定義は個々の実装に任せます。
とはいえ、@ GregBurghardtが指摘した回答をご覧ください。
ItemRepositoryクラスとMovieRepositoryクラスが実際に関連付けられていない限り、親クラスは必要ありません。
言い換えると、保存機能が一般的な実装ではない場合、これを抽象化しようとしても、メソッドの命名規則を強制しようとしても意味がありません。しかし、それが抽象化の目的ではありません。
できません。サブクラスは、追加の制限を課すことなく、親クラスと同じメソッドをサポートする必要があります。そうすれば、ItemRepository
型の変数に遭遇した場合、それがどのサブクラスであるかを知る必要はありません。いつでもsave
を呼び出してItem
を渡すことができます。
これが必要ない場合は、saveMovie(Movie $movie)
などの別のメソッドを追加できます。ただし、サブクラスが必要かどうかを再検討することもできます。
ItemRepository
クラスとMovieRepository
クラスが実際にに関連付けられていない限り、親クラスは必要ありません。代わりに、MovieRepository
でインターフェースを実装してください:
interface IMovieRepository
{
function save(Movie $movie);
}
class MovieRepository implements IMovieRepository
{
public function save(Movie $movie) {
// ...
}
}
ムービーリポジトリが必要な場所であれば、代わりにインターフェースを使用してください。コンストラクターインジェクションを利用するサンプルコントローラーは以下のとおりです。
class MoviesController
{
private $movies;
public __construct(IMovieRepository $movies = null) {
$this->movies = $movies || new MovieRepository();
}
public function create($params) {
$movie = new Movie();
// Populate $movie based on $params
$this->movies->save($movie);
}
}
どちらかと言えば、CRUDメソッドを実装せず、すべてのリポジトリクラスの共通機能を含むBaseRepository
クラスを作成します。あるエンティティを別のエンティティに保存すると変化するため、それらは関連しておらず、共通の親クラスを共有するべきではありません。
あなたはDoctrine2を学ぶことから利益を得るかもしれません。 「リポジトリパターン」も使用しますが、「データマッパーパターン」も使用します。
以下はエンティティーマネージャーの使用例です。
_/* $movieRepository = MovieRepository -> extends EntityRepository -> implements ObjectRepository */
if (null !== $movie = $movieRepository->findOneBy(array('name' => 'Mad Max'))) {
$movie = new Movie();
$movie->setName('Mad Max');
/* $entityManager = EntityManager -> implements ObjectManager */
$entityManager->persist($movie);
}
$movie->setViews($movie->getViews() + 1);
$entityManager->flush();
_
ここでDoctrine2は2つの主要なクラス(1つは抽象)を定義していることに注意してください。
_abstract class EntityRepository implements ObjectRepository
{
...
}
class EntityManager implements ObjectManager
{
...
}
_
伝統的に、リポジトリはクエリを作成するためにのみ使用されます。 ObjectRepository
インターフェイスは、一般的な選択メソッドを定義します:
find($id)
findBy(array $criteria)
findOneBy(array $criteria)
ただし、エンティティマネージャには、永続性に関連するメソッドがあります。
persist($entity)
flush($entity = null)
エンティティー・マネージャーは、データベースにマップされるものとして登録されているクラスの「範囲」以外に、特定のタイプのエンティティーを決して必要としません。
独自の永続化ライブラリを作成する場合は、_Doctrine\Common\Persistance
_インターフェイスを使用して設計を支援できます。
ObjectRepository
ObjectManager
persist
エンティティとflush
を一度に選択する理由は、プロセスを「最終化」する一貫したトランザクションを作成するためです。プロセス中に各エンティティを個別に「保存」するのではなく、アプリケーションでIO)の量を徐々に増やし、パフォーマンスに影響を与えます。
TL; DR
データマッパーパターンを設計に組み込み、永続性をリポジトリから移行します。代わりに、データベースの単一のマネージャーにカプセル化します。次に、リポジトリを選択クエリに制限します。