laravel-doctrine でDBからgetter, setter付きクラスの自動生成する


##経緯
ServiceとRepository間でデータのやり取りをする際に、型付きのオブジェクトを使うため、SymfonyのORMライブラリdoctrineを基にしたlaravelのライブラリである「laravel-doctrine/orm」を使用して、DBのスキーマから直接クラスをgetter, setter付きで自動生成する、というのを試してみました。

## 前提条件
Laravel5.8
laravel-doctrine/orm 1.4
MySQL8.*
laravel-doctrine/ormは開発で自動コード生成にのみ使用します。実際のDB操作には使用しない想定です。

## 1 laravel-doctrineの取得

```
composer require laravel-doctrine/orm --dev
```

## 2 config/doctrine.phpの生成、修正
```
php artisan vendor:publish --tag="config"

[出力]
Copied File [/vendor/laravel-doctrine/orm/config/doctrine.php] To [/config/doctrine.php]
Publishing complete.
```

下記のようにconfig/doctrine.phpを修正します。

```php
'managers'                   => [
        'default' => [
            'dev'           => env('APP_DEBUG', false),
            'meta'          => env('DOCTRINE_METADATA', 'annotations'),
            'connection'    => env('DB_CONNECTION', 'mysql'),
            'namespaces'    => ['App\Mappings'],
            'paths'         => [
                base_path('app/Mappings')
            ],
            'repository'    => Doctrine\ORM\EntityRepository::class,
            'proxies'       => [
                'namespace'     => false,
                'path'          => storage_path('proxies'),
                'auto_generate' => env('DOCTRINE_PROXY_AUTOGENERATE', false)
            ],
```

## 3 PrimaryKeyがないテーブルに対するエラー の回避
TableにPrimaryKeyが無いと、次のコマンドでDatabaseからマッピングのクラスをインポートする際に、下記のエラー が出る。

```
Doctrine\ORM\Mapping\MappingException : Table change_reason_master has no primary key. Doctrine does not support reverse engineering from tables that don't have a primary key.
```

これを避けるため、`vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\Driver\DatabaseDriver.php`を置き換えると自動生成が動くようでした。
（コードを生成後は元のコードに戻しておく、との記述がありました） 

参照：https://medium.com/@joaoneto/solves-doctrine-orm-error-with-tables-without-primary-key-on-mysql-when-mapping-the-database-1ce740610b51

```php
 //～～～～
           if ( ! $table->hasPrimaryKey()) {
                
                // throw new MappingException(
                //     "Table " . $table->getName() . " has no primary key. Doctrine does not ".
                //     "support reverse engineering from tables that don't have a primary key."
                // );
                
                
            }

            
            // $pkColumns = $table->getPrimaryKey()->getColumns();
            
            //ADD ↓↓↓
            $pkColumns = $table->hasPrimaryKey()? $table->getPrimaryKey()->getColumns() : [];
            //ADD ↑↑↑

//～～～～

    private function getTablePrimaryKeys(Table $table)
    {
        try {
            //DELETE ↓↓↓
            // return $table->getPrimaryKey()->getColumns();
            //DELETE ↑↑↑

            //ADD ↓↓↓
            if($table->hasPrimaryKey()){
                return $table->getPrimaryKey()->getColumns();
            }
            //ADD ↑↑↑
            
        } catch (SchemaException $e) {
            // Do nothing
        }

        return [];
    }
```

## 4 マッピングの作成

```
php artisan doctrine:mapping:import annotation
```

→app\Mapping直下にテーブルのEntityの元データ（getter, setterのメソッド無し）が作成される。
（これでもクラスとしては使用できるが全てprivate のプロパティのみのクラスとなってしまう。）

## 5 getter, setter付きクラスの生成

```
php artisan doctrine:generate:entities --generate-annotations --generate-methods
```

→app\Entities直下にテーブルのEntityの元データ（getter, setterのメソッド付き）が作成される
　（app\Mappingにあるphpファイルを元に生成される）


## 生成されたUsersクラス


```php
<?php



use Doctrine\ORM\Mapping as ORM;

/**
 * Users
 *
 * @ORM\Table(name="users")
 * @ORM\Entity
 */
class Users
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer", precision=0, scale=0, nullable=false, options={"unsigned"=true}, unique=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=50, precision=0, scale=0, nullable=false, unique=false)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=50, precision=0, scale=0, nullable=false, unique=false)
     */
    private $email;

    /**
     * @var \DateTime|null
     *
     * @ORM\Column(name="email_verified_at", type="datetime", precision=0, scale=0, nullable=true, unique=false)
     */
    private $emailVerifiedAt;

    /**
     * @var string|null
     *
     * @ORM\Column(name="password", type="string", length=100, precision=0, scale=0, nullable=true, unique=false)
     */
    private $password;

    /**
     * @var string|null
     *
     * @ORM\Column(name="remember_token", type="string", length=100, precision=0, scale=0, nullable=true, unique=false)
     */
    private $rememberToken;

    /**
     * @var \DateTime|null
     *
     * @ORM\Column(name="created_at", type="datetime", precision=0, scale=0, nullable=true, unique=false)
     */
    private $createdAt;

    /**
     * @var \DateTime|null
     *
     * @ORM\Column(name="updated_at", type="datetime", precision=0, scale=0, nullable=true, unique=false)
     */
    private $updatedAt;


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

    /**
     * Set name.
     *
     * @param string $name
     *
     * @return Users
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set email.
     *
     * @param string $email
     *
     * @return Users
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Get email.
     *
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set emailVerifiedAt.
     *
     * @param \DateTime|null $emailVerifiedAt
     *
     * @return Users
     */
    public function setEmailVerifiedAt($emailVerifiedAt = null)
    {
        $this->emailVerifiedAt = $emailVerifiedAt;

        return $this;
    }

    /**
     * Get emailVerifiedAt.
     *
     * @return \DateTime|null
     */
    public function getEmailVerifiedAt()
    {
        return $this->emailVerifiedAt;
    }

    /**
     * Set password.
     *
     * @param string|null $password
     *
     * @return Users
     */
    public function setPassword($password = null)
    {
        $this->password = $password;

        return $this;
    }

    /**
     * Get password.
     *
     * @return string|null
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * Set rememberToken.
     *
     * @param string|null $rememberToken
     *
     * @return Users
     */
    public function setRememberToken($rememberToken = null)
    {
        $this->rememberToken = $rememberToken;

        return $this;
    }

    /**
     * Get rememberToken.
     *
     * @return string|null
     */
    public function getRememberToken()
    {
        return $this->rememberToken;
    }

    /**
     * Set createdAt.
     *
     * @param \DateTime|null $createdAt
     *
     * @return Users
     */
    public function setCreatedAt($createdAt = null)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * Get createdAt.
     *
     * @return \DateTime|null
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set updatedAt.
     *
     * @param \DateTime|null $updatedAt
     *
     * @return Users
     */
    public function setUpdatedAt($updatedAt = null)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

    /**
     * Get updatedAt.
     *
     * @return \DateTime|null
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }
}

```

自動生成した際には、上書きしてしまうため、
自動生成後に別のディレクトリに移して
スキーマの変更があった時にはそのスキーマのクラスの変更部分のみ更新するのが良いかもしれません。

以上です。