ご挨拶!
最近、私はZend Framework 2の使用を開始しました。データベースの移行に対応するcliモジュールを作成する必要がありました。
この記事では、例として移行モジュールを使用してコマンドラインからZend 2で動作するモジュールを作成する方法、テストの作成方法、packagist.orgでモジュールを公開する方法について説明します
移行とは:データベースの移行は、データベースに対するアクションを記述し、これらのアクションを実行できるようにするクラスシステムです。
フレームワークのインストール
フレームワークをインストールすることから始めましょう。フレームワークとしてZendSkeletonApplicationを使用します
ZendSkeletonApplicationを複製します。これはアプリケーションのスケルトンです。
cd projects_dir /
git clone git://github.com/zendframework/ZendSkeletonApplication.git
// SampleZendModuleに名前を変更します
mv ZendSkeletonApplication SampleZendModule
//コンポーザを介してzendframework自体をインストールします
php composer.phar自己更新
php composer.pharインストール
基本的なインストールとクイックスタートの詳細については、こちらをご覧ください。
framework.zend.com/manual/2.0/en/index.htmlのユーザーガイドセクション
概要
Zend 2のコンソールタスクは、Web MVCと同様にMVCテクノロジーを使用して記述され、同様のルーティングシステムを使用します。これは、コンソールパラメーターの仕様によりわずかに異なります。
ルーターは、呼び出すコマンドを決定し、目的のコントローラーを呼び出して、すべてのデータを渡します。
通常、同じコントローラーがWebとコンソールに使用されますが、Zend \ Http \ Requestの代わりにZend \ Console \ Requestを使用する場合と、Zend \ Http \ Responseの代わりにZend \ Console \ Responseを使用する場合の違いは、それぞれ要求オブジェクトと応答オブジェクトです。
コンソールコマンドとのやり取りのポイントは、Webのやり取りを担当するものと同じ単一のエントリポイントです。 これは通常/project/public/index.phpです
モジュールワイヤフレームの作成
Zend 2には、コードを生成するためのコンソールユーティリティがないため、手動でモジュールを作成する必要があります。
プロジェクトのルートから次のディレクトリ構造を作成します
/プロジェクト/
-/ module /-モジュールのある共有フォルダー。デフォルトでは、必要なアプリケーションアプリケーションがあります。
---- / knyzev /-モジュールのグループまたは開発者の名前。まったく指定する必要はありませんが、packagist.orgで公開する場合、フォームグループ/パッケージの複合名が必要です。
------ / zend-db-migrations /-これはモジュールディレクトリそのものです
-------- / config /-構成用のフォルダー
-------- / src /-クラスのあるメインフォルダー
---------- / ZendDbMigrations /-名前空間に対応するディレクトリ
------------ /コントローラー/-コントローラー
------------ /ライブラリ/-移行用ライブラリ
------------ Module.php-モジュールに関する一般情報を提供するクラス
------------README.md-モジュールの説明
------------ composer.json-packagist.orgで公開できるように、モジュールと依存関係の説明
Zend 2では、アプリケーションはモジュールの形で構築され、それぞれがコントローラー、サービスなどを定義できます。
構成
configフォルダーから始めましょう。ここでは、configを含むmodule.config.phpファイルを作成する必要があります。そのようなファイルの内容があります。
<?php return array( 'migrations' => array( 'dir' => dirname(__FILE__) . '/../../../../migrations', 'namespace' => 'ZendDbMigrations\Migrations', 'show_log' => true ), 'console' => array( 'router' => array( 'routes' => array( 'db_migrations_version' => array( 'type' => 'simple', 'options' => array( 'route' => 'db_migrations_version [--env=]', 'defaults' => array( 'controller' => 'ZendDbMigrations\Controller\Migrate', 'action' => 'version' ) ) ), 'db_migrations_migrate' => array( 'type' => 'simple', 'options' => array( 'route' => 'db_migrations_migrate [<version>] [--env=]', 'defaults' => array( 'controller' => 'ZendDbMigrations\Controller\Migrate', 'action' => 'migrate' ) ) ), 'db_migrations_generate' => array( 'type' => 'simple', 'options' => array( 'route' => 'db_migrations_generate [--env=]', 'defaults' => array( 'controller' => 'ZendDbMigrations\Controller\Migrate', 'action' => 'generateMigrationClass' ) ) ) ) ) ), 'controllers' => array( 'invokables' => array( 'ZendDbMigrations\Controller\Migrate' => 'ZendDbMigrations\Controller\MigrateController' ), ), 'view_manager' => array( 'template_path_stack' => array( __DIR__ . '/../view', ), ), );
この設定では、コントローラーとview_managerはテンプレートの保存場所と呼び出されるコントローラーを記述します。このショートカットを理解すると、明らかに直接適用できます。これらのパラメーターはすべてのモジュールの標準です。
移行は、移行ストレージディレクトリを指定するモジュールの設定です。私の場合は、プロジェクトルートディレクトリ、移行クラスで指定されたネームスペース、show_logはコンソールへのログの出力を定義します。
コンソール-これはコンソールルーティングの構成です。Zend2では、コンソールパラメータの定義は、Webパーツで使用されるものと同様のルーティングシステムを介して行われます
コンソールルーティングの詳細については、こちらをご覧ください。
framework.zend.com/manual/2.0/en/modules/zend.console.routes.html
ここでの通常のhttpルーティングについて
framework.zend.com/manual/2.0/en/modules/zend.mvc.routing.html
そこで、ルートを作成します。 この場合、3つのルートが必要です
1. db_migrations_version-データベースの現在のバージョンに関する情報を表示します
2. db_migrations_migrate [] [--env =]-データベースの移行を実行またはロールバックします
3. db_migrations_generate-データベースのスタブを生成します
ルートパラメータの説明:
'db_migrations_migrate' => array( 'type' => 'simple', 'options' => array( 'route' => 'db_migrations_migrate [<version>] [--env=]', 'defaults' => array( 'controller' => 'ZendDbMigrations\Controller\Migrate', 'action' => 'migrate' ) ) ),
type-ルートのタイプ
options / route-パラメータとオプションを含むコンソールコマンドの名前。オプションのパラメータが角括弧で囲まれている場合、詳細な説明は上記のリンクに記載されています。
オプション/デフォルト/コントローラー-コントローラー処理ルート
オプション/デフォルト/アクション-コントローラーのアクション
コントローラー
<?php /** * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/ZendSkeletonApplication for the canonical source repository * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace ZendDbMigrations\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Zend\Console\Request as ConsoleRequest; use ZendDbMigrations\Library\Migration; use ZendDbMigrations\Library\MigrationException; use ZendDbMigrations\Library\GeneratorMigrationClass; use ZendDbMigrations\Library\OutputWriter; /** * */ class MigrateController extends AbstractActionController { /** * * @return \Migrations\Library\Migration */ protected function getMigration(){ $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter'); $config = $this->getServiceLocator()->get('Configuration'); $console = $this->getServiceLocator()->get('console'); $output = null; if($config['migrations']['show_log']) { $output = new OutputWriter(function($message) use($console) { $console->write($message . "\n"); }); } return new Migration($adapter, $config['migrations']['dir'], $config['migrations']['namespace'], $output); } /** * * @return integer */ public function versionAction(){ $migration = $this->getMigration(); return sprintf("Current version %s\n", $migration->getCurrentVersion()); } /** * */ public function migrateAction(){ $migration = $this->getMigration(); $version = $this->getRequest()->getParam('version'); if(is_null($version) && $migration->getCurrentVersion() >= $migration->getMaxMigrationNumber($migration->getMigrationClasses())) return "No migrations to execute.\n"; try{ $migration->migrate($version); return "Migrations executed!\n"; } catch (MigrationException $e) { return "ZendDbMigrations\Library\MigrationException\n" . $e->getMessage() . "\n"; } } /** * */ public function generateMigrationClassAction(){ $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter'); $config = $this->getServiceLocator()->get('Configuration'); $generator = new GeneratorMigrationClass($config['migrations']['dir'], $config['migrations']['namespace']); $className = $generator->generate(); return sprintf("Generated class %s\n", $className); } }
典型的なコントローラーの例は次のとおりです。ルーティングルートが接続されるアクションは、[name] Actionという形式の名前を持ち、Actionは必須の部分であり、nameはコマンドの名前です。
要求パラメーターは、Zend / Console / Requestクラスを通じて、コントローラーの継承された基本クラスを通じて取得されます
$ this-> getRequest()-> getParam( 'version')-これは、db_migrations_migrate []ルートからバージョンパラメーターを取得する方法です。
この例のように、プレーンテキストの形式でメソッドから返されたものはすべて、ViewModelにラップされ、コンソールに直接表示されます。
アプリケーションの実行中にコンソールに非同期出力するには、サービスロケーター$ this-> getServiceLocator()-> get( 'console')を介して利用可能なZend / Console / Responseを使用する必要があります。write、writeAt、writeLineメソッドをサポートします。 詳細な説明とパラメーターは、ドキュメントに記載されています。
Module.php
<?php namespace ZendDbMigrations; use Zend\Mvc\ModuleRouteListener; use Zend\ModuleManager\Feature\AutoloaderProviderInterface; use Zend\ModuleManager\Feature\ConfigProviderInterface; use Zend\ModuleManager\Feature\ConsoleUsageProviderInterface; use Zend\Console\Adapter\AdapterInterface as Console; use Zend\ModuleManager\Feature\ConsoleBannerProviderInterface; class Module implements AutoloaderProviderInterface, ConfigProviderInterface, ConsoleUsageProviderInterface, ConsoleBannerProviderInterface { public function onBootstrap($e) { $e->getApplication()->getServiceManager()->get('translator'); $eventManager = $e->getApplication()->getEventManager(); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); } public function getConfig() { return include __DIR__ . '/config/module.config.php'; } public function getAutoloaderConfig() { return array( 'Zend\Loader\StandardAutoloader' => array( 'namespaces' => array( __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, ), ), ); } public function getConsoleBanner(Console $console){ return 'DB Migrations Module'; } public function getConsoleUsage(Console $console){ //description command return array( 'db_migrations_version' => 'Get current migration version', 'db_migrations_migrate [<version>]' => 'Execute migrate', 'db_migrations_generate' => 'Generate new migration class' ); } }
Module.phpファイルは、モジュールに関するいくつかの情報を提供します。すべてのModule.phpファイルは、構成ファイルやその他のデータをロードするために、各起動時に自動的にロードされます。
この場合、Moduleクラスは次のようになります。
パラメーターなしでコンソールスクリプトを呼び出すときに既存のすべてのコマンドのリストを表示するには、ConsoleUsageProviderInterfaceインターフェイスとその実装のサポートをモジュールに追加する必要があります。これにより、上記の例のような説明を含むコマンドの配列が出力されます。
たとえば、次のコマンドを実行すると
php public / index.php
モジュールのgetConsoleUsageメソッドが返すすべてのコマンドが表示されます。
PHPUnitテストの作成
MVC Zend 2のテストは通常、プロジェクトのルートにあるテストフォルダーにあり、モジュールの構造に完全に対応しています。
例えば
/プロジェクト/
-/モジュール/
-/ knyzev /
--- / zend-db-migrations /
---- / src /
----- / ZendDbMigrations /
------ /コントローラー/
------- / MigrateController.php
-/テスト/
-/ knyzev /
--- / zend-db-migrations /
---- / src /
----- / ZendDbMigrations /
------ /コントローラー/
------- / MigrateControllerTest.php
そして、MigrateControllerクラスのテストの例を示します
<?php namespace Tests\ZendDbMigrations\Controller; use ZendDbMigrations\Controller\MigrateController; use Zend\Console\Request as ConsoleRequest; use Zend\Console\Response; use Zend\Mvc\MvcEvent; use Zend\Mvc\Router\RouteMatch; use PHPUnit_Framework_TestCase; use \Bootstrap; use Zend\Db\Adapter\Adapter; use Zend\Db\Metadata\Metadata; /** * MigrateController */ class MigrateControllerTest extends PHPUnit_Framework_TestCase { protected $controller; protected $request; protected $response; protected $routeMatch; protected $event; protected $eventManager; protected $serviceManager; protected $dbAdapter; protected $connection; protected $metadata; protected $folderMigrationFixtures; /** * */ protected function setUp() { $bootstrap = \Zend\Mvc\Application::init(Bootstrap::getAplicationConfiguration()); $this->request = new ConsoleRequest(); $this->routeMatch = new RouteMatch(array('controller' => 'migrate')); $this->event = $bootstrap->getMvcEvent(); $this->event->setRouteMatch($this->routeMatch); $this->eventManager = $bootstrap->getEventManager(); $this->serviceManager = $bootstrap->getServiceManager(); $this->dbAdapter = $bootstrap->getServiceManager()->get('Zend\Db\Adapter\Adapter'); $this->connection = $this->dbAdapter->getDriver()->getConnection(); $this->metadata = new Metadata($this->dbAdapter); $this->folderMigrationFixtures = dirname(__FILE__) . '/../MigrationsFixtures'; $this->initController(); $this->tearDown(); } protected function tearDown(){ $this->dbAdapter->query('DROP TABLE IF EXISTS migration_version CASCADE;', Adapter::QUERY_MODE_EXECUTE); $this->dbAdapter->query('DROP TABLE IF EXISTS test_migrations CASCADE;', Adapter::QUERY_MODE_EXECUTE); $this->dbAdapter->query('DROP TABLE IF EXISTS test_migrations2 CASCADE;', Adapter::QUERY_MODE_EXECUTE); $iterator = new \GlobIterator($this->folderMigrationFixtures . '/tmp/*', \FilesystemIterator::KEY_AS_FILENAME); foreach ($iterator as $item) { if($item->isFile()) { unlink($item->getPath() . '/' . $item->getFilename()); } } chmod($this->folderMigrationFixtures . '/tmp', 0775); } protected function initController(){ $this->controller = new MigrateController(); $this->controller->setEvent($this->event); $this->controller->setEventManager($this->eventManager); $this->controller->setServiceLocator($this->serviceManager); } /** * */ public function testVersion() { $this->routeMatch->setParam('action', 'version'); $result = $this->controller->dispatch($this->request); $response = $this->controller->getResponse(); $this->assertEquals(200, $response->getStatusCode(), 'Status code is 200 OK!'); $this->assertInstanceOf('Zend\View\Model\ViewModel', $result, 'Method return object Zend\View\Model\ViewModel!'); $this->assertEquals("Current version 0\n", $result->getVariable('result'), 'Returt value is correctly!'); // $this->connection->execute('INSERT INTO migration_version (version) VALUES (12345678910)'); // $result = $this->controller->dispatch($this->request); $response = $this->controller->getResponse(); $this->assertEquals("Current version 12345678910\n", $result->getVariable('result'), 'Returt value is correctly!'); } /** * */ public function testMigrateIfNotMigrations() { $this->routeMatch->setParam('action', 'migrate'); $result = $this->controller->dispatch($this->request); $response = $this->controller->getResponse(); $this->assertEquals(200, $response->getStatusCode(), 'Status code is 200 OK!'); $this->assertInstanceOf('Zend\View\Model\ViewModel', $result, 'Method return object Zend\View\Model\ViewModel!'); $this->assertEquals("No migrations to execute.\n", $result->getVariable('result'), 'Return correct info if no exists not executable migations!'); } /** * */ public function testMigrationIfExistsMigrations(){ // copy($this->folderMigrationFixtures . '/MigrationsGroup1/Version20121110210200.php', $this->folderMigrationFixtures . '/tmp/Version20121110210200.php'); $this->routeMatch->setParam('action', 'migrate'); $result = $this->controller->dispatch($this->request); $response = $this->controller->getResponse(); $this->assertEquals(200, $response->getStatusCode(), 'Status code is 200 OK!'); $this->assertEquals("Migrations executed!\n", $result->getVariable('result'), 'Return correct info if executed migrations!'); // $this->assertTrue(in_array('test_migrations', $this->metadata->getTableNames()), 'Migration real executed!'); // $this->initController(); $this->routeMatch->setParam('action', 'migrate'); $this->routeMatch->setParam('version', 20121110210200); $result = $this->controller->dispatch($this->request); $response = $this->controller->getResponse(); $this->assertEquals(200, $response->getStatusCode(), 'Status code is 200 OK!'); $this->assertContains("Migration version 20121110210200 is current version!\n", $result->getVariable('result'), 'Starting the migration with a current version works correctly!'); } /** * */ public function testMigrateWithVersion() { copy($this->folderMigrationFixtures . '/MigrationsGroup2/Version20121111150900.php', $this->folderMigrationFixtures . '/tmp/Version20121111150900.php'); copy($this->folderMigrationFixtures . '/MigrationsGroup2/Version20121111153700.php', $this->folderMigrationFixtures . '/tmp/Version20121111153700.php'); $this->routeMatch->setParam('action', 'migrate'); $this->routeMatch->setParam('version', 20121111150900); $result = $this->controller->dispatch($this->request); $response = $this->controller->getResponse(); $this->assertEquals(200, $response->getStatusCode(), 'Status code is 200 OK!'); $this->assertTrue(in_array('test_migrations', $this->metadata->getTableNames()), 'Migration 20121111150900 execucte ok!'); $this->assertFalse(in_array('test_migrations2', $this->metadata->getTableNames()), 'Migration 20121111153700 not execucte ok!'); } /** * */ public function testGenerateMigrationClass() { $this->routeMatch->setParam('action', 'generateMigrationClass'); $result = $this->controller->dispatch($this->request); $response = $this->controller->getResponse(); $this->assertEquals(200, $response->getStatusCode(), 'Status code is 200 OK!'); $this->assertInstanceOf('Zend\View\Model\ViewModel', $result, 'Method return object Zend\View\Model\ViewModel!'); $this->assertContains("Generated class ", $result->getVariable('result'), 'Return result info ok!'); $fileName = sprintf('Version%s.php', date('YmdHis', time())); $this->assertFileExists($this->folderMigrationFixtures . '/tmp/' . $fileName, 'Generate command real generated class!'); } }
テストの構造の詳細については、こちらをご覧ください。
framework.zend.com/manual/2.0/en/user-guide/unit-testing.html
ここには微妙な違いがあります。Zend2は環境での作業をサポートしていません。そのため、テストベースで作業するには独自の自転車を考案する必要があります。
Composer.jsonおよびpackagist.orgでのモジュールの追加
次に、json composerでモジュールを記述して公開する必要があります。
次の情報を使用して、モジュールルートにcomposer.jsonファイルを作成します
{ "name": "knyzev/zend-db-migrations", "description": "Module for managment database migrations.", "type": "library", "license": "BSD-3-Clause", "keywords": [ "database", "db", "migrations", "zf2" ], "homepage": "https://github.com/vadim-knyzev/ZendDbMigrations", "authors": [ { "name": "Vadim Knyzev", "email": "vadim.knyzev@gmail.com", "homepage": "http://vadim-knyzev.blogspot.com/" } ], "require": { "php": ">=5.3.3", "zendframework/zendframework": "2.*" }, "autoload": { "psr-0": { "ZendDbMigrations": "src/" }, "classmap": [ "./Module.php" ] } }
name-モジュールの名前。モジュールフォルダーの名前に対応します。
require-依存関係
残りは似たようなものにコピーして説明できます。
次に、 github.comでアカウントを登録し、パブリックリポジトリを選択して、MyZendModuleという形式の名前を入力します
ローカルコンピューターでgitリポジトリを開始し、すべてをgithubに送信します
git init
git remote add origin github.com/knyzev/zend-db-migrations
git add -A
git commit -m "Init commit"
git push
packagist.orgに登録し、サブミットパッケージを選択してgithubにリンクを追加すると、composer.jsonの正確性を自動的にチェックし、問題がある場合は報告します。
すべてが、現在は新しいプロジェクトまたは他の誰かがメインファイルcomposer.jsonで実行できます
knyzev / zend-db-migrationsのような依存関係を追加するだけです
コマンドを実行する
php composer.phar自己更新
php composer.pharアップデート
モジュールは自動的にインストールされ、config / application.config.phpに登録するだけです。
Symfony 2 + Doctrine 2とZend 2の比較
私はSymfony 2と第2バージョンのDoctrineが本当に好きで、アノテーション、完全なコンソールサポート(すべてのケースでコンソールコマンド)、かなり便利なサービスの宣言、Doctrine ORMシステム、zendはかなり悲観的で快適ではありませんが、これは個人的な主観的な意見ただし、場所でより高速に動作し、メモリの消費量が少ない場合があります。 この印象は、主にクイックスタートに対する不完全さ、つまり すべてを自分で設定して完了する必要があります。
Symfonyで少し作業した後、Java Spring + Hibernateに切り替える可能性について考え始めました。
この記事で説明されている移行モジュール自体は、ここで表示できます。
github.com/vadim-knyzev/ZendDbMigrations
次のように、テストはモジュールに含まれていません。 zend 2モジュールの標準構造の標準に従って、テストは別のフォルダーに配置されます。
PS:zendのウェブサイトmodules.zendframework.comのモジュール情報ページにモジュールを追加する方法を知っていますか?
framework.zend.com/manual/2.0/en/index.html
github.com/vadim-knyzev/ZendDbMigrations
vadim-knyzev.blogspot.com