私はYiiフレームワークが本当に好きです。 高速、便利、柔軟です。 ActiveRecordパターンの実装方法が気に入っています。 しかし、ビジネスロジック、正確にはドメインロジックが非常に複雑であり、絶えず成長および修正されている場合があります。 このような場合、DataMapperパターンを使用する方が便利です。
同時に、Doctrine 2 ORMが好きです。 これはおそらく、最も幅広い機能を備えたPHP向けの最も強力なORMです。 はい、おそらく「重い」ため、アプリケーションの速度が低下します。 しかし、開発を始めるには、まず、アプリケーションのアーキテクチャについて考える必要があります。 「時期尚早な最適化がすべての悪の根源です」
したがって、ある日、これらの興味深いツールのうち2つを私のために接続することになりました。 これがどのように行われたかは、以下で説明されています。
必要なライブラリをインストールする
対応するDoctrineComponentコンポーネントを作成することでDoctrineとYiiを接続し、Doctrine関数へのアクセスを提供することになりました。
まず、ベンダーフォルダーは保護されたフレームワークフォルダーに作成され、Doctrine 2 ORMコードがアップロードされました。 Composerを使用するか、単にGitHub Doctrineプロジェクトからソースコードをダウンロード/クローンすることでDoctrineをインストールできます。
また、ORMが正しく機能するためには、 Doctrine Database Abstraction LayerとDoctrine Commonが必要です(Composerを使用してDoctrine 2 ORMをインストールすると、これらの依存関係は自動的にプルされます)。
さらに、コンソールからDoctrine 2 ORMを操作できるように、同じフォルダーにSymfonyコンポーネントのvendor 2フォルダーをインストールすることをお勧めします-これらはコンソール (コンソールからDoctrineを操作するため)とYaml (Yamlでエンティティを記述したい場合)です
したがって、この段階で、次のプロジェクト構造を取得する必要があります。
DoctrineComponentコンポーネントの作成
これで、DoctrineComponentコンポーネントの作成に直接進むことができます。 かなり小さいので、以下にコンポーネントコード全体を示します。 このコードはDoctrineComponent.phpファイルのprotected / componentsフォルダーに配置する必要があります。
DoctrineComponent.phpファイルコード
use Doctrine\ORM\EntityManager; use Doctrine\ORM\Configuration; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; class DoctrineComponent extends CComponent { private $em = null; private $basePath; private $proxyPath; private $entityPath; private $driver; private $user; private $password; private $host; private $dbname; public function init() { $this->initDoctrine(); } public function initDoctrine() { Yii::setPathOfAlias('Doctrine', $this->getBasePath() . '/vendor/Doctrine'); $cache = new Doctrine\Common\Cache\FilesystemCache($this->getBasePath() . '/cache'); $config = new Configuration(); $config->setMetadataCacheImpl($cache); $driverImpl = new AnnotationDriver(new AnnotationReader(), $this->getEntityPath()); AnnotationRegistry::registerAutoloadNamespace('Doctrine\ORM\Mapping', $this->getBasePath() . '/vendor'); $config->setMetadataDriverImpl($driverImpl); $config->setQueryCacheImpl($cache); $config->setProxyDir($this->getProxyPath()); $config->setProxyNamespace('Proxies'); $config->setAutoGenerateProxyClasses(true); $connectionOptions = array( 'driver' => $this->getDriver(), 'user' => $this->getUser(), 'password' => $this->getPassword(), 'host' => $this->getHost(), 'dbname' => $this->getDbname() ); $this->em = EntityManager::create($connectionOptions, $config); } public function setBasePath($basePath) { $this->basePath = $basePath; } public function getBasePath() { return $this->basePath; } public function setEntityPath($entityPath) { $this->entityPath = $entityPath; } public function getEntityPath() { return $this->entityPath; } public function setProxyPath($proxyPath) { $this->proxyPath = $proxyPath; } public function getProxyPath() { return $this->proxyPath; } public function setDbname($dbname) { $this->dbname = $dbname; } public function getDbname() { return $this->dbname; } public function setDriver($driver) { $this->driver = $driver; } public function getDriver() { return $this->driver; } public function setHost($host) { $this->host = $host; } public function getHost() { return $this->host; } public function setPassword($password) { $this->password = $password; } public function getPassword() { return $this->password; } public function setUser($user) { $this->user = $user; } public function getUser() { return $this->user; } /** * @return EntityManager */ public function getEntityManager() { return $this->em; } }
コンポーネントの主要部分は、
initDoctrine
メソッドで囲まれています。 コードをさらに詳しく分析しましょう。
$cache = new Doctrine\Common\Cache\FilesystemCache($this->getBasePath() . '/cache'); $config = new Configuration(); $config->setMetadataCacheImpl($cache);
このコードを使用して、Doctrineからエンティティメタデータをキャッシュするメソッドを確立します。 良い方法では、キャッシュのタイプ(この場合は
FilesystemCache
)をコンポーネントパラメーターに配置する必要があります。これは、コンポーネントの構成時に変更できます。
$driverImpl = new AnnotationDriver(new AnnotationReader(), $this->getEntityPath()); AnnotationRegistry::registerAutoloadNamespace('Doctrine\ORM\Mapping', $this->getBasePath() . '/vendor'); $config->setMetadataDriverImpl($driverImpl);
上記のコードを使用して、エンティティメタデータを読み取るためのドライバーがインストールされます。
$config->setQueryCacheImpl($cache); $config->setProxyDir($this->getProxyPath()); $config->setProxyNamespace('Proxies'); $config->setAutoGenerateProxyClasses(true);
上記のコードでは、リクエストのキャッシュ方法を設定します(最初の行)、残りの行はDoctrineのプロキシ設定(パス、名前空間、プロキシクラスの自動生成)です
$connectionOptions = array( 'driver' => $this->getDriver(), 'user' => $this->getUser(), 'password' => $this->getPassword(), 'host' => $this->getHost(), 'dbname' => $this->getDbname() ); $this->em = EntityManager::create($connectionOptions, $config);
上記のコードは、データベースに接続するためのオプションを定義しています。 これらのパラメーターは、コンポーネントの接続時に設定されます(コンポーネントの接続方法については後で説明します)。
最後に、以前の
$connectionOptions
と
$config
定義されたEntityManagerが作成され、エンティティを使用して作業できます。
DoctrineComponentをプロジェクトに接続する方法は?
DoctrineComponent
をプロジェクトに接続することに移りましょう。
これを行うには非常に簡単です-プロジェクトの構成ファイルに変更を加えるだけです(通常、これはmain.phpです)
return array( 'components' => array( 'doctrine'=>array( 'class' => 'DoctrineComponent', 'basePath' => __DIR__ . '/../', 'proxyPath' => __DIR__ . '/../proxies', 'entityPath' => array( __DIR__ . '/../entities' ), 'driver' => 'pdo_mysql', 'user' => 'dbuser', 'password' => 'dbpassword', 'host' => 'localhost', 'dbname' => 'somedb' ), // ... );
これで、コンポーネントは
Yii::app()->doctrine
から利用できるようになり、
Yii::app()->doctrine->getEntityManager()
から
EntityManager
を取得できます
しかし、コンポーネントをこのように使用すると、
EntityManager
オブジェクトのメソッドヒントに問題が発生します。 このために、次のソリューションが発明されました。
lass MainController extends Controller { private $entityManager = null; /** * @return Doctrine\ORM\EntityManager */ public function getEntityManager() { if(is_null($this->entityManager)){ $this->entityManager = Yii::app()->doctrine->getEntityManager(); } return $this->entityManager; } // ... }
各コントローラーは
MainController
から継承されるようになったため、各コントローラーで
$this->getEntityManager()
メソッドを呼び出してエンティティマネージャーを取得でき、
EntityManager
の
$this->getEntityManager()
がIDEで機能するようになります。これは間違いなくプラスです。
Doctrine Consoleの設定
Doctrineはコンソールを介して作業するのに非常に便利です。 ただし、このためには、実行するコードを記述する必要があります。 このコードを以下に示します。 ファイルを保護/コマンドフォルダーにコンソールを起動するために配置します。 コンソールをさらに簡単に起動するために
doctrine
コマンドを実装するのも良いでしょうが、私はまだこれをしていません。
Doctrineコンソールを操作するためのサンプルの
doctrine.php
ファイル。
Doctrine.phpファイルコード
// change the following paths if necessary $yii = __DIR__ .'path/to/yii.php'; $config = __DIR__ . 'path/to/config/console.php'; require_once($yii); Yii::createWebApplication($config); Yii::setPathOfAlias('Symfony', Yii::getPathOfAlias('application.vendor.Symfony')); $em = Yii::app()->doctrine->getEntityManager(); $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); \Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);
Doctrineコンソールを起動するには、コマンドフォルダーに移動して
php doctrine.php
を実行します。
GridViewウィジェットでのモデルの検証とモデルの使用。
Doctrine 2 ORMで作業した人は、一般に受け入れられている概念(検証方法、データベースからのデータの取得、有効化されたビジネスロジックなど)に実際にはモデルがなく、この機能は実際には2つの部分に分かれていることを知っています-
Entity
および
Repository
。
Entity
は通常、ビジネスロジックが含まれ、
Repository
、DBAL Doctrineを使用してデータベースからデータを取得するメソッド(エンティティマネージャーまたは他のメソッド)が含まれます。
モデル検証
したがって、私の意見では、特定のエンティティのクラスにデータ検証を含めることは論理的です。
User
エンティティの例を考えてみましょう。
車輪を再発明しないために、Yiiから、
CModel.
クラスからすでに組み込まれているモデル検証を使用するのが良いと判断されました
CModel.
これを行うには、CModelクラスからUserエンティティを継承するだけです。 以下に説明する検証ルールを持つそのようなエンティティの例:
ユーザーエンティティコード
次に、この検証を使用する方法の例を示します(以下に新しいユーザーを作成する例)。
use Doctrine\ORM\Mapping as ORM; /** * User * * @ORM\Table(name="user") * @ORM\Entity(repositoryClass="UserRepository") * @ORM\HasLifecycleCallbacks */ class User extends CModel { /** * @var integer * * @ORM\Column(name="id", type="integer", nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ private $id; /** * @var string * * @ORM\Column(name="name", type="string", length=255, nullable=false) */ private $name; /** * @var string * * @ORM\Column(name="password", type="string", length=255, nullable=false) */ private $password; /** * @var string * * @ORM\Column(name="email", type="string", length=255, nullable=false) */ private $email; /** * @var string * * @ORM\Column(name="role", type="string", length=255, nullable=false) */ private $role; /** * @var \DateTime * * @ORM\Column(name="created", type="datetime", nullable=false) */ private $created; /** * @var \DateTime * * @ORM\Column(name="modified", type="datetime", nullable=false) */ private $modified; public function rules(){ return array( array('name, password', 'required'), // ... ); } public function attributeNames() { return array( 'id'=>'id', 'name'=>'name', 'email'=>'email', 'created'=>'created', 'updated'=>'updated' ); } public function attributeLabels() { return array( 'description' => 'Description', 'createdString' => 'Creation Date' ); } // ... }
次に、この検証を使用する方法の例を示します(以下に新しいユーザーを作成する例)。
/** * Creates a new model. * If creation is successful, the browser will be redirected to the 'view' page. */ public function actionCreate() { $user = new User(); $userData = $this->getRequest()->get('User'); $course->setAttributes($userData); if(!is_null($userData) && $user->validate()) { $user->setName($userData['name']); // ... $this->getEntityManager()->persist($user); $this->getEntityManager()->flush(); $this->redirect(array('view','id'=>$user->getId())); } $this->render('create',array( 'model'=>$user, )); }
GridViewウィジェットでモデルを使用する
Yiiの主な魅力の1つは、私の意見では、ウィジェット、特にさまざまなグリッドです。
ただし、唯一の注意点は、
ActiveRecord
で動作することです(つまり、
GridView
ウィジェットを意味し
GridView
)。 そして個人的には、それらをDoctrineやエンティティで動作させたいと思います。 これには
Repository
を使用できます。
GridView
を使用する
GridView
2つのボトルネックがあり
GridView
dataProvider
と
filter
プロパティです。 そして、ここでYii開発者に歌を歌います-GridViewが
ActiveRecord
から取得したデータ以外のある種のデータを操作するには、
dataProvider
として
GridView
渡されるオブジェクトが
IDataProvider
インターフェースを正しく実装するだけで十分です(このインターフェースは
UserRepository
で実装する必要があります
UserRepository
)、および
filter
渡されるオブジェクトは
CModel
から継承
filter
必要があります(このために、
User
エンティティは既に素晴らしいです)。
UserRepository
実装全体を説明するのではなく、一般的なスキームのみを概説します。
BaseRepositoryクラスコード
use Doctrine\ORM\EntityRepository; abstract class BaseRepository extends EntityRepository implements IDataProvider { protected $_id; private $_data; private $_keys; private $_totalItemCount; private $_sort; private $_pagination; public $modelClass; public $model; public $keyAttribute; private $_criteria; private $_countCriteria; public $data; abstract protected function fetchData(); abstract protected function calculateTotalItemCount(); public function getId(){ //... } public function getPagination($className='CPagination'){ //... } public function setPagination($value){ //... } public function setSort($value){ //... } public function getData($refresh=false){ //... } public function setData($value){ //... } public function getKeys($refresh=false){ //... } public function setKeys($value){ //... } public function getItemCount($refresh=false){ //... } public function getTotalItemCount($refresh=false){ //... } public function setTotalItemCount($value){ //... } public function getCriteria(){ //... } public function setCriteria($value){ //... } public function getCountCriteria(){ //... } public function setCountCriteria($value){ //... } public function getSort($className='CSort'){ //... } protected function fetchKeys(){ //... } private function _getSort($className){ //... } }
上記は、基本的なリポジトリの実装例です。 実際、多くのメソッドの実装は、
IDataProvider
インターフェイスを実装するYiiクラス
CActiveDataProvider
で見ることができます。
UserRepository
2つのメソッドのみを定義する必要があります(以下のサンプルコード):
<?php class UserRepository extends BaseRepository { protected $_id = 'UserRepository'; /** * Fetches the data from the persistent data storage. * @return array list of data items */ protected function fetchData() { //... } /** * Calculates the total number of data items. * @return integer the total number of data items. */ protected function calculateTotalItemCount() { //... } }
まとめ
上記では、Yii + Doctrine 2 ORMバンドルでの作業方法の1つを示しました。 Doctrine 2 ORM Yiiにより利点が失われると多くの人々が言うことができますが、Doctrineには最適化とキャッシュのための膨大なツールがあり、プレーンSQLへの過度に遅いまたは集中的なクエリの書き換えを禁止する人はいないことを忘れないでください。
しかし、そのようなバンドルでは、アーキテクチャソリューションで勝ち、私の意見では、コードはこれからきれいになります。
コメントで、DataMapperパターンを実装するためのオプション、Yiiの他のいくつかのORM、YiiのActiveRecordモデルでのビジネスロジックの成長を解決する方法、Yiiを使用したサブジェクト指向プログラミングについて共有してくれたらとても感謝しています。
ご清聴ありがとうございました。