laravelでdoctrine

# はじめに
https://github.com/laravel-doctrine/orm を使ってlaravelでDoctrineしてみた。

# インストール
Laravelプロジェクトの作成。

```bash
curl -s "https://laravel.build/laravel-doctrine" | bash
cd laravel-doctrine
```

依存ライブラリのインストール。

```
sail composer req doctrine/inflector:^1.4 laravel-doctrine/orm laravel-doctrine/migrations
sail artisan vendor:publish --tag="config"
```


# EntityとRepositoryの作成

## Entity

`larave-doctrine`を使う時は`Eloquent`のモデルを用いません。代わりに`app/Entities/`以下にモデルの代わりのクラスであるエンティティを作成します。エンティティにはアノテーションでカラム情報などのメタデータを付与する必要がありますが、基本的にはPOPOとして定義できるのが`Eloquent`との大きな違いです。また、カラムとのマッピングはymlやxmlで定義することもでき、そのようにするとエンティティは純粋なPOPOになります。メタデータに関しては[こちら](http://www.laraveldoctrine.org/docs/1.4/orm/meta-data)を参照ください。

laravelのレスポンスに直接渡せるように`Illuminate\Contracts\Support\Arrayable`をimplementsしておくと便利です。

```php:app/Entities/User.php
<?php

namespace App\Entities;

use Doctrine\ORM\Mapping as ORM;
use Illuminate\Contracts\Support\Arrayable;

/**
 * @ORM\Entity
 */
class User implements Arrayable
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $name;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name): self
    {
        $this->name = $name;
        return $this;
    }

    public function toArray()
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
        ];
    }
}

```

## Repository
Doctrineを使った設計では、基本的に一つのエンティティに対して一つのリポジトリを作ります。リポジトリクラスでは以下のように`Doctrine\ORM\EntityRepository`を継承し、コンストラクタで親クラスに`EntityManagerInterface`とメタデータを渡します。

また、`Doctrine\ORM\EntityRepository`には`find()`、`findAll()`、`findBy()`、`findOneBy()`、`count()`などのよく使うメソッドがあらかじめ定義されているので、これらのメソッドは自分で実装する必要がありません。

```php:app/Repository/UserRepository.php
<?php

namespace App\Repository;

use App\Entities\User;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository
{
    public function __construct(EntityManagerInterface $em)
    {
        parent::__construct($em, $em->getClassMetadata(User::class));
    }
}

```

# マイグレーションの作成と実行

`Eloquent`を使う時はマイグレーションファイルは自前で書く必要がありました。`Doctrine`では、エンティティに付与したメタデータと実際のDBとの差分を見て自動でマイグレーションを作ってくれます（めちゃくちゃ便利）。

```bash
sail artisan doctrine:migrations:diff
sail artisan doctrine:migrations:migrate
```

# 簡単な例

コントローラーを作成し、ルートに登録します。

```php:routes/api.php
<?php

use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;

Route::group(['auth:api'], function () {
    Route::resource('users', UserController::class)->only([
        'index', 'store', 'show', 'destroy'
    ]);;
});

```


コントローラーの各メソッドでは、先ほど作ったリポジトリをメソッドインジェクションできます。リポジトリには`findXXX()`系のメソッドが既に定義されているので、簡単な検索ならメソッドを追加するまでもありません。

リポジトリはデータソースとしての役割のみ持っていますので、「データの更新」や「データの削除」などDBへの書き込みはエンティティマネージャーを通して行う必要があります。エンティティマネージャーはlaravel-doctrineでFacadeとして提供されているので`EntityManager::persist($user)`や`EntityManager::flush()`のように使うことも可能です。

```php:app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use App\Entities\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Illuminate\Http\JsonResponse;

class UserController extends Controller
{
    public function index(UserRepository $repository): JsonResponse
    {
        $users = $repository->findAll();
        
        return response()->json(collect($users));
    }

    public function store(EntityManagerInterface $entityManager): JsonResponse
    {
        $user = new User();
        $user->setName('test');

        $entityManager->persist($user);
        $entityManager->flush();

        return response()->json($user);
    }

    public function show($id, UserRepository $repository): JsonResponse
    {
        $user = $repository->find($id);
        
        return response()->json($user);
    }

    public function destroy($id, UserRepository $repository, EntityManagerInterface $entityManager): JsonResponse
    {
        $user = $repository->find($id);
        if (!$user) {
            return response()->json([], 404);
        }
        
        $entityManager->remove($user);
        $entityManager->flush();

        return response()->json();
    }
}
```

こんな感じに動きます。

```bash
$ curl http://localhost/api/users
[]

$ curl http://localhost/api/users -X POST
{"id":1,"name":"test"}

$ curl http://localhost/api/users -X POST
{"id":2,"name":"test"}

$ curl http://localhost/api/users -X POST
{"id":3,"name":"test"}

$ curl http://localhost/api/users/2
[{"id":2,"name":"test"}]

$ curl http://localhost/api/users/2 -X DELETE
[]

$ curl http://localhost/api/users
[{"id":1,"name":"test"},{"id":3,"name":"test"}]
```

# 感想
`Eloquent`を使うメリットはやはりLaravelとの親和性がとても高いところです。しかし、リポジトリパターンを使おうとすると、`Eloquent`の便利メソッド系はリポジトリ内でしか使えないという暗黙のルールができてしまいます。それなら最初からリポジトリを考慮した設計のORM使った方がよくない？と思い今回laravel-doctrineを触ってみました。

まだ本当に基本的なことしかしてないですが、ORMとしての機能はまったく問題なさそうな感じがします。また、エンティティには`Illuminate\Contracts\Support\Arrayable`をはじめとするインターフェイス類をimplementsさせればLaravelとの親和性も思ったほど低くならないかも？とも思いました。

もう少し色んなパターンで触ってみます。



# 続編
https://qiita.com/ggg-mzkr/items/abec896a0032894998a0