EC-CUBE4　管理画面のCRUD　脱初心者三歩目　其ノ壱　Entity編

#最初に
脱初心者の二歩目として、基本的なCRUDを作りたいと思います。
基本的な事が分かれば、ある程度本体のソースも読むことが出来るのではないでしょうか？

特に、EntityやForm、Controller等に関して理解出来るようになれば、ちょっとしたカスタマイズが簡単になります。

最初の頃は色んなカスタム方法をつまみ食いしてましたが、大きなカスタムをするには、ある程度上から下まで読めるようにならなければ難しかったです。
トリッキーなカスタムをしなければならない方もいるかもですが、まずは、基本的な事を押さえてみましょう！！

以下のカスタム例を作成しながら、僕がいつも考える（作成する）順番にいくつかの記事に分けて書きます。
ここでは、新しいPHPの書き方はやらないです。本体ソースに合わせています。

#カスタム例
代理店情報を管理する機能を追加するカスタムをしようと思います。
仕様は以下の通り。

###仕様

####代理店情報
・代理店コード（半角英数字）
・代理店名
・担当者名(姓・名)
・住所（郵便番号・都道府県・住所１・住所２）
・メールアドレス
・電話番号
・FAX

上記情報を管理します。

隠れたデータとして、

・ID
・登録日時
・編集日時
・登録管理者

の情報が保存されます。

####画面
・一覧（検索）
・登録
・編集（登録と共通）
・削除（一覧でモーダル画面を表示する）

４項目ですが、登録と編集は共通、削除は一覧でモーダル表示するので、実質２画面です。
動作的に参考になるのが、会員管理の動きになります。

###最初に作るはEntity
仕様が決まれば、次にデータベースの設計をします。
設計が終われば、テーブルに合わせたEntityを作成します。

（ここでは、DBなどについては、詳しく説明しないです。）

###Entityの作成例
参考にする本体ソース：[Eccube/Entity/News](https://github.com/EC-CUBE/ec-cube/blob/4.0/src/Eccube/Entity/News.php)
NewsのEntityは基本的な形として分かり易いかなと思います。

**本体Entityの拡張については、ドキュメントがあるので、そちらをご覧ください。**
[Entityのカスタマイズ - < for EC-CUBE 4.0 Developers />](https://doc4.ec-cube.net/customize_entity)

####Classのアノテーションでテーブルの設定

```php
/**
 * News
 *
 * @ORM\Table(name="dtb_news")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discriminator_type", type="string", length=255)
 * @ORM\HasLifecycleCallbacks()
 * @ORM\Entity(repositoryClass="Eccube\Repository\NewsRepository")
 */
```

`@ORM\Table(name="dtb_news")`でテーブル名
`@ORM\Entity(repositoryClass="Eccube\Repository\NewsRepository")`でRepositoryを設定します。

`@ORM\Cache(usage="NONSTRICT_READ_WRITE")`は基本的になくて良いと思います。

#####インデックスやユニークを追加
参考にする本体ソース：[Eccube/Entity/Cart](https://github.com/EC-CUBE/ec-cube/blob/4.0/src/Eccube/Entity/Cart.php)

```php
/**
 * @ORM\Table(name="dtb_cart", indexes={
 *     @ORM\Index(name="dtb_cart_update_date_idx", columns={"update_date"})
 *  },
 *  uniqueConstraints={
 *     @ORM\UniqueConstraint(name="dtb_cart_pre_order_id_idx", columns={"pre_order_id"})
 *  }))
 */
```

CartのEntityはindexとユニークを追加しています。
インデクスを複数設定する場合は`@ORM\Index`をカンマで区切って追加するだけです。
ユニークも同じだと思いますが、２個も３個も追加する事は無いかと思います。
（必須ではないです。必要な時に、必要な分だけ追加しましょう。）

####カラムの設定

カラムは様々な設定が有りますが、EC-CUBE本体のEntityファイルを読みまくれば、基本的には対応出来ると思います。
オートインクリメントのid、文字列、テキスト、数値、小数点、ブーリアン、日付時間などなど。

```php:id
/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer", options={"unsigned":true})
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="IDENTITY")
 */
private $id;
```

```php:文字列
/**
 * @var string|null
 *
 * @ORM\Column(name="company_name", type="string", length=255, nullable=true)
 */
private $company_name;
```

```php:テキスト
/**
 * @var string|null
 *
 * @ORM\Column(name="message", type="string", length=4000, nullable=true)
 */
private $message;
```

```php:数値
/**
 * @var int
 *
 * @ORM\Column(name="sort_no", type="integer", options={"unsigned":true})
 */
private $sort_no;
```

```php:小数点
/**
 * @var string
 *
 * @ORM\Column(name="price", type="decimal", precision=12, scale=2, options={"default":0})
 */
private $price = 0;
```

```php:ブーリアン
/**
 * @var boolean
 *
 * @ORM\Column(name="visible", type="boolean", options={"default":true})
 */
private $visible;
```

```php:日付時間
/**
 * @var \DateTime
 *
 * @ORM\Column(name="create_date", type="datetimetz")
 */
private $create_date;
```

[Eccube/Entity](https://github.com/EC-CUBE/ec-cube/tree/4.0/src/Eccube/Entity)から参考に抜き出しています。
このままコピペでも良いのですが、`nullable=true`や`options={"default":true}`など細かい設定もあります。
必要に応じて変更してください。
(DBの知識が関わってきます。)

#####ゲッターとセッター

`get〇〇` `set〇〇`といった関数が実装されてます。
（idは自動採番されるオートインクリメントなので、セッターは無かったりします。）

例としてcompany_nameのゲッターとセッターを見てみましょう。

```php
/**
 * Set companyName.
 *
 * @param string|null $companyName
 *
 * @return BaseInfo
 */
public function setCompanyName($companyName = null)
{
    $this->company_name = $companyName;

    return $this;
}

/**
 * Get companyName.
 *
 * @return string|null
 */
public function getCompanyName()
{
    return $this->company_name;
}
```

〇〇の部分は、プロパティ名をキャメルケースで作成します。
この形をプロパティ名に合わせて作成すれば良いと思います。
（いつもコピペして、変更してます。）

```php:ブーリアン
/**
 * @return integer
 */
public function isVisible()
{
     return $this->visible;
}
```

ブーリアンの場合、setではなくisになっています。
tureかfalseか、PHPの関数でも`is_〇〇`なんてのがありますね。

####リレーション
#####多対１（ManyToOne）
例として`Customer`と`Status`を見ていきます。

Customerは複数の会員データを持ち、そのデータにStatus（仮登録・本登録・退会）のどれか一つのデータと紐づきます。

参考本体ソース:[Eccube/Entity/Customer](https://github.com/EC-CUBE/ec-cube/blob/e20433c5645299d6f83b66f3e129d1c49dfda398/src/Eccube/Entity/Customer.php#L234)

```php
/**
 * @var \Eccube\Entity\Master\CustomerStatus
 *
 * @ORM\ManyToOne(targetEntity="Eccube\Entity\Master\CustomerStatus")
 * @ORM\JoinColumns({
 *   @ORM\JoinColumn(name="customer_status_id", referencedColumnName="id")
 * })
 */
private $Status;
```

`@ORM\ManyToOne(targetEntity="Eccube\Entity\Master\CustomerStatus")`でリレーション先のEntityを設定しています。
`@ORM\JoinColumn(name="customer_status_id", referencedColumnName="id")`のnameでstatusのidを保存するカラム名、referencedColumnNameでリレーション先のカラムを指定します。（基本的にはidになると思います。）

######ゲッターとセッター

ゲッターは通常のカラムと同じですが、セッターはちょっと違います。
ManyToOneの場合、セットするデータはリレーション先のEntityオブジェクトになります。
なので、セッターはオブジェクトの型宣言がされています。

本来なら、通常カラムも型宣言した方がよいかもですね。

```php:ゲッターとセッター
/**
 * Set status.
 *
 * @param \Eccube\Entity\Master\CustomerStatus|null $status
 *
 * @return Customer
 */
public function setStatus(\Eccube\Entity\Master\CustomerStatus $status = null)
{
    $this->Status = $status;

    return $this;
}

/**
 * Get status.
 *
 * @return \Eccube\Entity\Master\CustomerStatus|null
 */
public function getStatus()
{
    return $this->Status;
}
```

#####１対多（OneToMany）
例として`Product`と`ProductCategory`を見ていきます。
`ProductCategory`は`Prioduct`と`Category`をつなぐ中間テーブルになり、`Product_id`と`Category_id`を持っています。

参考本体ソース:[Eccube/Entity/Product](https://github.com/EC-CUBE/ec-cube/blob/e20433c5645299d6f83b66f3e129d1c49dfda398/src/Eccube/Entity/Product.php#L511)

```php
/**
 * @var \Doctrine\Common\Collections\Collection
 *
 * @ORM\OneToMany(targetEntity="Eccube\Entity\ProductCategory", mappedBy="Product", cascade={"persist","remove"})
 */
private $ProductCategories;
```

ProductからProductCategoryをgetした場合、複数のProductCategoryがCollectionタイプで取得されます。
なのでプロパティ名は複数系になってます。

`@ORM\OneToMany(targetEntity="Eccube\Entity\ProductCategory", mappedBy="Product", cascade={"persist","remove"})`でリレーションの設定をします。

targetEntityでリレーション先のEntityを設定
mappedByはリレーション先の[プロパティ名](https://github.com/EC-CUBE/ec-cube/blob/e20433c5645299d6f83b66f3e129d1c49dfda398/src/Eccube/Entity/ProductCategory.php#L56)

cascadeはDBの知識がちょと必要かなぁと思います。

**cascadeにpersistを設定した場合**、コレクション内のすべてのエンティティを永続化します。
ProductにProductCategoryをaddし、Productを永続化したらProductCategoryの永続化されます。
[ここ](https://github.com/EC-CUBE/ec-cube/blob/e20433c5645299d6f83b66f3e129d1c49dfda398/src/Eccube/Controller/Admin/Product/ProductController.php#L505)と[ここ](https://github.com/EC-CUBE/ec-cube/blob/e20433c5645299d6f83b66f3e129d1c49dfda398/src/Eccube/Controller/Admin/Product/ProductController.php#L514)でProductCategoryをpersistしてますが、この２行をコメントアウトして、[ここ](https://github.com/EC-CUBE/ec-cube/blob/e20433c5645299d6f83b66f3e129d1c49dfda398/src/Eccube/Controller/Admin/Product/ProductController.php#L521)に下記２行を追記。

```php
$this->entityManager->persist($Product);
$this->entityManager->flush();
```
商品登録・編集でカテゴリ設定の動作に問題は無いので、説明は間違ってないかと思います。
（ちょっと自信が無いので、詳しい方がいたらコメントください。）

**cascadeにremoveを設定した場合**、Productを削除した時、ProductCategoryも削除されます。
[Eccube/Controller/Admin/ProductController::delete()](https://github.com/EC-CUBE/ec-cube/blob/e20433c5645299d6f83b66f3e129d1c49dfda398/src/Eccube/Controller/Admin/Product/ProductController.php#L680)では、ProductCategoryを削除している部分がありません。
自動でProductに紐づくProductCategoryを削除してくれます。

cascadeは他にも種類があるのですが、EC-CUBEでは利用されてなかったので、説明は省きます。
気になる方は検索してみてください。
（ぶっちゃけ、利用してないので、動作を調べてないです。）

######ゲッターとセッター
Collectionタイプなので、`__construct()`で初期化します。

```php
$this->ProductCategories = new \Doctrine\Common\Collections\ArrayCollection();
```

ゲッターは変わらずですが、セッターはオブジェクトを追加・削除するので、addとremoveになります。

```php
/**
 * Add productCategory.
 *
 * @param \Eccube\Entity\ProductCategory $productCategory
 *
 * @return Product
 */
public function addProductCategory(\Eccube\Entity\ProductCategory $productCategory)
{
    $this->ProductCategories[] = $productCategory;

    return $this;
}

/**
 * Remove productCategory.
 *
 * @param \Eccube\Entity\ProductCategory $productCategory
 *
 * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
 */
public function removeProductCategory(\Eccube\Entity\ProductCategory $productCategory)
{
    return $this->ProductCategories->removeElement($productCategory);
}

/**
 * Get productCategories.
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getProductCategories()
{
    return $this->ProductCategories;
}
```

以上の設定が分かれば、テーブルを作るという点では、問題なくカスタマイズできると思います。

####SSHからコマンドを打ってデータベースに反映

下記コマンドを打って、設置したEntityをDBに反映させます。
[こちらの記事から転載](https://qiita.com/dev_belltzel/items/b8bf262ca8615d382736)
（自分の記事です）

**Proxy クラスを生成**
`bin/console eccube:generate:proxies`

**キャッシュクリア**
`bin/console cache:clear --no-warmup`

**実行する SQL を確認**
`bin/console doctrine:schema:update --dump-sql`

**SQL を実行**
`bin/console doctrine:schema:update --dump-sql --force`

**キャッシュ生成**
`bin/console cache:warmup`

###仕様のEntity

仕様に合わせたEntityは以下になります。

```php:Customize\Entity\Agency.php
<?php

namespace Customize\Entity;

use Doctrine\ORM\Mapping as ORM;

if (!class_exists('\Customize\Entity\Agency')) {
    /**
     * Agency
     *
     * @ORM\Table(name="customize_agency",uniqueConstraints={@ORM\UniqueConstraint(columns={"code"})})
     * @ORM\InheritanceType("SINGLE_TABLE")
     * @ORM\DiscriminatorColumn(name="discriminator_type", type="string", length=255)
     * @ORM\HasLifecycleCallbacks()
     * @ORM\Entity(repositoryClass="Customize\Repository\AgencyRepository")
     */
    class Agency extends \Eccube\Entity\AbstractEntity
    {
        /**
         * @var int
         *
         * @ORM\Column(name="id", type="integer", options={"unsigned":true})
         * @ORM\Id()
         * @ORM\GeneratedValue(strategy="IDENTITY")
         */
        private $id;

        /**
         * @var string
         *
         * @ORM\Column(name="code", type="string", length=255)
         */
        private $code;

        /**
         * @var string|null
         *
         * @ORM\Column(name="_name", type="string", length=255, nullable=true)
         */
        private $name;

        /**
         * @var string
         *
         * @ORM\Column(name="contact_person_name01", type="string", length=255)
         */
        private $contact_person_name01;

        /**
         * @var string
         *
         * @ORM\Column(name="contact_person_name02", type="string", length=255)
         */
        private $contact_person_name02;

        /**
         * @var string|null
         *
         * @ORM\Column(name="postal_code", type="string", length=8, nullable=true)
         */
        private $postal_code;

        /**
         * @var string|null
         *
         * @ORM\Column(name="addr01", type="string", length=255, nullable=true)
         */
        private $addr01;

        /**
         * @var string|null
         *
         * @ORM\Column(name="addr02", type="string", length=255, nullable=true)
         */
        private $addr02;

        /**
         * @var string
         *
         * @ORM\Column(name="email", type="string", length=255)
         */
        private $email;

        /**
         * @var string|null
         *
         * @ORM\Column(name="phone_number", type="string", length=14, nullable=true)
         */
        private $phone_number;

        /**
         * @var string|null
         *
         * @ORM\Column(name="fax_number", type="string", length=14, nullable=true)
         */
        private $fax_number;

        /**
         * @var \DateTime
         *
         * @ORM\Column(name="create_date", type="datetimetz")
         */
        private $create_date;

        /**
         * @var \DateTime
         *
         * @ORM\Column(name="update_date", type="datetimetz")
         */
        private $update_date;

        /**
         * @var Member
         *
         * @ORM\ManyToOne(targetEntity="Eccube\Entity\Member")
         * @ORM\JoinColumns({
         *   @ORM\JoinColumn(name="creator_id", referencedColumnName="id")
         * })
         */
        private $Creator;

        /**
         * @var \Eccube\Entity\Master\Pref
         *
         * @ORM\ManyToOne(targetEntity="Eccube\Entity\Master\Pref")
         * @ORM\JoinColumns({
         *   @ORM\JoinColumn(name="pref_id", referencedColumnName="id")
         * })
         */
        private $Pref;

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

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

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

        /**
         * Set code.
         *
         * @param string $code
         *
         * @return $this
         */
        public function setCode($code)
        {
            $this->code = $code;

            return $this;
        }

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

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

            return $this;
        }

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

        /**
         * Set contact_person_name01.
         *
         * @param string $contact_person_name01
         *
         * @return $this
         */
        public function setContactPersonName01($contact_person_name01)
        {
            $this->contact_person_name01 = $contact_person_name01;

            return $this;
        }

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

        /**
         * Set contact_person_name02.
         *
         * @param string $contact_person_name02
         *
         * @return $this
         */
        public function setContactPersonName02($contact_person_name02)
        {
            $this->contact_person_name02 = $contact_person_name02;

            return $this;
        }

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

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

            return $this;
        }

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

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

            return $this;
        }

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

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

            return $this;
        }

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

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

            return $this;
        }

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

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

            return $this;
        }

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

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

            return $this;
        }

        /**
         * Get createDate.
         *
         * @return \DateTime
         */
        public function getCreateDate()
        {
            return $this->create_date;
        }

        /**
         * Set createDate.
         *
         * @param \DateTime $createDate
         *
         * @return $this
         */
        public function setCreateDate($createDate)
        {
            $this->create_date = $createDate;

            return $this;
        }

        /**
         * Get updateDate.
         *
         * @return \DateTime
         */
        public function getUpdateDate()
        {
            return $this->update_date;
        }

        /**
         * Set updateDate.
         *
         * @param \DateTime $updateDate
         *
         * @return $this
         */
        public function setUpdateDate($updateDate)
        {
            $this->update_date = $updateDate;

            return $this;
        }

        /**
         * Get creator.
         *
         * @return \Eccube\Entity\Member|null
         */
        public function getCreator()
        {
            return $this->Creator;
        }

        /**
         * Set creator.
         *
         * @param \Eccube\Entity\Member|null $creator
         *
         * @return $this
         */
        public function setCreator(\Eccube\Entity\Member $creator = null)
        {
            $this->Creator = $creator;

            return $this;
        }

        /**
         * Get pref.
         *
         * @return \Eccube\Entity\Master\Pref|null
         */
        public function getPref()
        {
            return $this->Pref;
        }

        /**
         * Set pref.
         *
         * @param \Eccube\Entity\Master\Pref|null $pref
         *
         * @return $this
         */
        public function setPref(\Eccube\Entity\Master\Pref $pref = null)
        {
            $this->Pref = $pref;

            return $this;
        }
    }
}
```

上記Entityを`Customize/Entity`に設置してEntityを反映させるコマンドを打ってください。
データベースに`customize_agency`というテーブルができているはずです。
カラムなども比較してみてください。

この時点では、Repositoryを作成してなくてもいいはず。
Repositoryに関しては次の記事で説明します。

#最後に
以上が僕なりのEntityの説明になります。
最初の頃はリレーション部分をどの様に設定すればいいのかさっぱりでした。

そんな人の役に立てばいいなぁと思っています。

それでは、次回をお楽しみに！！