#最初に
脱初心者の二歩目として、基本的なCRUDを作りたいと思います。
基本的な事が分かれば、ある程度本体のソースも読むことが出来るのではないでしょうか?
特に、EntityやForm、Controller等に関して理解出来るようになれば、ちょっとしたカスタマイズが簡単になります。
最初の頃は色んなカスタム方法をつまみ食いしてましたが、大きなカスタムをするには、ある程度上から下まで読めるようにならなければ難しかったです。
トリッキーなカスタムをしなければならない方もいるかもですが、まずは、基本的な事を押さえてみましょう!!
以下のカスタム例を作成しながら、僕がいつも考える(作成する)順番にいくつかの記事に分けて書きます。
ここでは、新しいPHPの書き方はやらないです。本体ソースに合わせています。
#カスタム例
代理店情報を管理する機能を追加するカスタムをしようと思います。
仕様は以下の通り。
###仕様
####代理店情報
・代理店コード(半角英数字)
・代理店名
・担当者名(姓・名)
・住所(郵便番号・都道府県・住所1・住所2)
・メールアドレス
・電話番号
・FAX
上記情報を管理します。
隠れたデータとして、
・ID
・登録日時
・編集日時
・登録管理者
の情報が保存されます。
####画面
・一覧(検索)
・登録
・編集(登録と共通)
・削除(一覧でモーダル画面を表示する)
4項目ですが、登録と編集は共通、削除は一覧でモーダル表示するので、実質2画面です。
動作的に参考になるのが、会員管理の動きになります。
###最初に作るはEntity
仕様が決まれば、次にデータベースの設計をします。
設計が終われば、テーブルに合わせたEntityを作成します。
(ここでは、DBなどについては、詳しく説明しないです。)
###Entityの作成例
参考にする本体ソース:Eccube/Entity/News
NewsのEntityは基本的な形として分かり易いかなと思います。
本体Entityの拡張については、ドキュメントがあるので、そちらをご覧ください。
Entityのカスタマイズ - < for EC-CUBE 4.0 Developers />
####Classのアノテーションでテーブルの設定
/**
* 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
/**
* @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
をカンマで区切って追加するだけです。
ユニークも同じだと思いますが、2個も3個も追加する事は無いかと思います。
(必須ではないです。必要な時に、必要な分だけ追加しましょう。)
####カラムの設定
カラムは様々な設定が有りますが、EC-CUBE本体のEntityファイルを読みまくれば、基本的には対応出来ると思います。
オートインクリメントのid、文字列、テキスト、数値、小数点、ブーリアン、日付時間などなど。
/**
* @var integer
*
* @ORM\Column(name="id", type="integer", options={"unsigned":true})
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string|null
*
* @ORM\Column(name="company_name", type="string", length=255, nullable=true)
*/
private $company_name;
/**
* @var string|null
*
* @ORM\Column(name="message", type="string", length=4000, nullable=true)
*/
private $message;
/**
* @var int
*
* @ORM\Column(name="sort_no", type="integer", options={"unsigned":true})
*/
private $sort_no;
/**
* @var string
*
* @ORM\Column(name="price", type="decimal", precision=12, scale=2, options={"default":0})
*/
private $price = 0;
/**
* @var boolean
*
* @ORM\Column(name="visible", type="boolean", options={"default":true})
*/
private $visible;
/**
* @var \DateTime
*
* @ORM\Column(name="create_date", type="datetimetz")
*/
private $create_date;
Eccube/Entityから参考に抜き出しています。
このままコピペでも良いのですが、nullable=true
やoptions={"default":true}
など細かい設定もあります。
必要に応じて変更してください。
(DBの知識が関わってきます。)
#####ゲッターとセッター
get〇〇
set〇〇
といった関数が実装されてます。
(idは自動採番されるオートインクリメントなので、セッターは無かったりします。)
例としてcompany_nameのゲッターとセッターを見てみましょう。
/**
* 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;
}
〇〇の部分は、プロパティ名をキャメルケースで作成します。
この形をプロパティ名に合わせて作成すれば良いと思います。
(いつもコピペして、変更してます。)
/**
* @return integer
*/
public function isVisible()
{
return $this->visible;
}
ブーリアンの場合、setではなくisになっています。
tureかfalseか、PHPの関数でもis_〇〇
なんてのがありますね。
####リレーション
#####多対1(ManyToOne)
例としてCustomer
とStatus
を見ていきます。
Customerは複数の会員データを持ち、そのデータにStatus(仮登録・本登録・退会)のどれか一つのデータと紐づきます。
参考本体ソース:Eccube/Entity/Customer
/**
* @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オブジェクトになります。
なので、セッターはオブジェクトの型宣言がされています。
本来なら、通常カラムも型宣言した方がよいかもですね。
/**
* 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;
}
#####1対多(OneToMany)
例としてProduct
とProductCategory
を見ていきます。
ProductCategory
はPrioduct
とCategory
をつなぐ中間テーブルになり、Product_id
とCategory_id
を持っています。
参考本体ソース:Eccube/Entity/Product
/**
* @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はリレーション先のプロパティ名
cascadeはDBの知識がちょと必要かなぁと思います。
cascadeにpersistを設定した場合、コレクション内のすべてのエンティティを永続化します。
ProductにProductCategoryをaddし、Productを永続化したらProductCategoryの永続化されます。
こことここでProductCategoryをpersistしてますが、この2行をコメントアウトして、ここに下記2行を追記。
$this->entityManager->persist($Product);
$this->entityManager->flush();
商品登録・編集でカテゴリ設定の動作に問題は無いので、説明は間違ってないかと思います。
(ちょっと自信が無いので、詳しい方がいたらコメントください。)
cascadeにremoveを設定した場合、Productを削除した時、ProductCategoryも削除されます。
Eccube/Controller/Admin/ProductController::delete()では、ProductCategoryを削除している部分がありません。
自動でProductに紐づくProductCategoryを削除してくれます。
cascadeは他にも種類があるのですが、EC-CUBEでは利用されてなかったので、説明は省きます。
気になる方は検索してみてください。
(ぶっちゃけ、利用してないので、動作を調べてないです。)
######ゲッターとセッター
Collectionタイプなので、__construct()
で初期化します。
$this->ProductCategories = new \Doctrine\Common\Collections\ArrayCollection();
ゲッターは変わらずですが、セッターはオブジェクトを追加・削除するので、addとremoveになります。
/**
* 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に反映させます。
こちらの記事から転載
(自分の記事です)
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
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の説明になります。
最初の頃はリレーション部分をどの様に設定すればいいのかさっぱりでした。
そんな人の役に立てばいいなぁと思っています。
それでは、次回をお楽しみに!!