昨日は、symfonyアプリケーションの国際化とローカライゼーションのしかたを学びました。 再度繰り返しますが、ICU標準とたくさんのヘルパーのおかげで、symfonyではこの作業は本当に楽です。
今日は、プラグインは何ができ、プラグインに何をまとめることができるのか、何のために使うことができるのかについて説明します。
プラグイン
symfonyのプラグイン
symfonyプラグインはプロジェクトファイルのサブセットのパッケージを作成して配布する方法を提供します。 プロジェクトのように、プラグインはクラス、ヘルパー、設定、タスク、モジュール、スキーマとアセットさえも格納できます。
プライベートプラグイン
プラグインの最初の使い方は複数のアプリケーションもしくは異なるプロジェクトの間でコードを楽に共有できるようにすることです。 symfonyアプリケーションはモデルのみを共有することを思い思い出してください。 プラグインはさらに複数のアプリケーションの間でコンポーネントを共有する方法を提供します。
異なるプロジェクトもしくは同じモジュールの同じスキーマを再利用する場合、これらをプラグインに移動させます。
プラグインは単なるディレクトリなので、SVNリポジトリを作りsvn:externalsを指定するもしくは1つのプロジェクトから他のプロジェクトにファイルをコピーすることでプラグインを移動させることができます。
私たちはこれらを"プライベート(非公開の)プラグイン"と呼んでいます。 これらの用途は1人の開発者もしくは企業に限定されているからです。 これらは公に利用できません。
tip
プライベートプラグインからパッケージを作ることもできます。
独自のsymfonyプラグインチャンネルを作成し、plugin:installタスクをとおしてこれらをインストールします。
公開プラグイン
公開プラグインはコミュニティでダウンロードとインストールができます。
このチュートリアルでは、ひとくみの公開プラグイン: sfDoctrineGuardPluginとsfFormExtraPluginを使いました。
これらはプライベートプラグインとまったく同じです。 唯一の違いは誰でもプロジェクトにインストールできることです。 後で公開プラグインを公開してsymfonyの公式サイトでホストする方法を見ることになります。
コードを編成する異なる方法
プラグインを考え出す方法とそれらの使い方はいろいろあります。
再利用と共有を忘れましょう。プラグインはあなたのコードを編成するための異なる方法として利用できます。
レイヤーごとにファイルを編成する代わりに: lib/model/ディレクトリのすべてのモデル、templates/ディレクトリのテンプレートなど; ファイルは機能ごとにまとめられます: すべてのjobファイルのセット(モデル、モジュール、テンプレート)、すべてのCMSファイルのセットなど。
プラグインのファイル構造
プラグインはファイルの性質に応じて、あらかじめ定義された構造にわかれるファイルを持つ単なるディレクトリ構造です。
今日は、Jobeet用に書いてきた大部分のコードをsfJobeetPluginに移動させます。
使用する基本的なレイヤーは次の通りです:
sfJobeetPlugin/
config/
sfJobeetPluginConfiguration.class.php // プラグインの初期化
routing.yml // ルーティング
doctrine/
schema.yml // データベーススキーマ
lib/
Jobeet.class.php // クラス
helper/ // ヘルパー
filter/ // フィルタークラス
form/ // フォームクラス
model/ // モデルクラス
task/ // タスク
modules/
job/ // モジュール
actions/
config/
templates/
web/ // JS、CSSと画像のようなアセット
Jobeetプラグイン
プラグインのブートストラップはシンプルでplugins/の下で新しいディレクトリを作ります。
Jobeetに関して、sfJobeetPluginディレクトリを作りましょう:
$ mkdir plugins/sfJobeetPlugin
note
すべてのプラグインの名前はPluginで終わらなければなりません。
義務ではありませんが、プレフィックスとしてsfをつけるのはよい習慣です。
Model
最初に、config/doctrine/schema.ymlファイルをplugins/sfJobeetPlugin/config/に移動させます:
$ mkdir plugins/sfJobeetPlugin/config/ $ mkdir plugins/sfJobeetPlugin/config/doctrine $ mv config/doctrine/schema.yml plugins/sfJobeetPlugin/config/doctrine/schema.yml
note
すべてのコマンドはUnix系環境のものです。
Windowsを利用しているのであれば、Explorerでファイルをドラッグ&ドロップできます。
コードを管理するのにSubversion、もしくは他のツールを使う場合、
これらが提供する組み込みのツールを使います(ファイルを移動させるsvn mvなど)。
モデル、フォーム、フィルターファイルをplugins/sfJobeetPlugin/lib/に移動させます:
$ mkdir plugins/sfJobeetPlugin/lib/ $ mv lib/model/ plugins/sfJobeetPlugin/lib/ $ mv lib/form/ plugins/sfJobeetPlugin/lib/ $ mv lib/filter/ plugins/sfJobeetPlugin/lib/
モデル、フォームとフィルターを移動させた後でクラスを抽象クラスにし、リネームしてプレフィックスとしてPluginをつけなければなりません。
tip
プレフィックスのPluginをつけるのは自動生成クラスのみですべてのクラスではありません。
たとえば手で書いたクラスにはプレフィックスをつけないでください。
プレフィックスが必要なのは自動生成されたクラスのみです。
JobeetAffiliateとJobeetAffiliateTableクラスを移動させる例は次の通りです。
$ mv plugins/sfJobeetPlugin/lib/model/doctrine/JobeetAffiliate.class.php plugins/sfJobeetPlugin/lib/model/doctrine/PluginJobeetAffiliate.class.php
コードは次のように更新されます:
abstract class PluginJobeetAffiliate extends BaseJobeetAffiliate { public function preValidate($event) { $object = $event->getInvoker(); if (!$object->getToken()) { $object->setToken(sha1($object->getEmail().rand(11111, 99999))); } } // ... }
JobeetAffiliateTableクラスを移動させましょう:
$ mv plugins/sfJobeetPlugin/lib/model/doctrine/JobeetAffiliateTable.class.php plugins/sfJobeetPlugin/lib/model/doctrine/PluginJobeetAffiliateTable.class.php
クラスの定義は次のようになります:
abstract class PluginJobeetAffiliateTable extends Doctrine_Table { // ... }
フォームとフィルタークラスにも同じことを行います。
Pluginをプレフィックスとしてつけるためにこれらをリネームします。
form、filterとmodelディレクトリに対してplugins/sfJobeetPlugin/lib/*/doctrine/のbaseディレクトリを必ず削除してください:
$ rm -rf plugins/sfJobeetPlugin/lib/form/doctrine/base $ rm -rf plugins/sfJobeetPlugin/lib/filter/doctrine/base $ rm -rf plugins/sfJobeetPlugin/lib/model/doctrine/base
フォーム、フィルターとモデルクラスを移動させ、リネームして一部を削除したのですべてのクラスをリビルドするタスクを実行します:
$ php symfony doctrine:build-model $ php symfony doctrine:build-forms $ php symfony doctrine:build-filters
いくつかの新しく作成されたディレクトリがlib/model/doctrine/sfJobeetPlugin/に格納されるスキーマから作成されたモデルを保有こしていることがわかります。
このディレクトリはスキーマから生成されたトップレベルのモデルと基底クラスを含みます。
たとえばJobeetJobモデルは次のようなクラス構造を持ちます:
JobeetJob(PluginJobeetJobを継承)lib/model/doctrine/sfJobeetPlugin/JobeetJob.class.php: すべてのプロジェクトの機能が設定できるトップレベルのクラスです。 ここは機能を追加したりプラグインモデルに付属する機能をオーバーライドできる場所です。PluginJobeetJob(BaseJobeetJobを継承)plugins/sfJobeetPlugin/lib/model/doctrine/PluginJobeetJob.class.php: このクラスはプラグイン固有の機能すべてを格納します。JobeetJobクラスを修正することでこのクラスと基底クラスをオーバーライドできます。BaseJobeetJob(sfDoctrineRecordを継承)lib/model/doctrine/sfJobeetPlugin/base/BaseJobeetJob.class.php:doctrine:build-modelを実行するたびにYAMLスキーマファイルから自動生成される基底クラス。JobeetJobTable(PluginJobeetJobTableを継承)lib/model/doctrine/sfJobeetPlugin/JobeetJobTable.class.php:Doctrine::getTable('JobeetJob')を呼び出す際に返されるDoctrine_Tableのインスタンスであることを除いてJobeetJobクラスと同じPluginJobeetJobTable(Doctrine_Tableを継承)lib/model/doctrine/sfJobeetPlugin/JobeetJobTable.class.php: このクラスはDoctrine::getTable('JobeetJob')を呼び出す際に返されるDoctrine_Tableのインスタンス用のプラグイン固有の機能をすべて含みます
この生成された構造によってトップレベルのJobeetJobクラスを編集することでプラグインのモデルをカスタマイズできます。
setTableDefinition()とsetUp()メソッドをオーバーライドすることでスキーマとカラムをカスタマイズし、リレーションを追加できます。
note
フォームクラスを移動させるとき、かならずconfigure()メソッドをsetup()メソッドに変更しparent::setup()を呼び出してください。
下記のコードは例です。
abstract class PluginJobeetAffiliateForm extends BaseJobeetAffiliateForm { public function setup() { parent::setup(); } // ... }
すべてのDoctrineフォームが基底クラスを持たないことを確認する必要があります。
これらのファイルはプロジェクトに対してグローバルでありdoctrine:build-formsとdoctrine:build-filtersで再生成されます。
プラグインからファイルを削除します:
$ rm plugins/sfJobeetPlugin/lib/form/doctrine/BaseFormDoctrine.class.php $ rm plugins/sfJobeetPlugin/lib/filter/doctrine/BaseFormFilterDoctrine.class.php
note
symfony 1.2.0もしくは1.2.1を利用する場合、フィルターの基底クラスはplugins/sfJobeetPlugin/lib/filter/base/ディレクトリにあります。
Jobeet.class.phpファイルをプラグインに移動させることもできます:
$ mv lib/Jobeet.class.php plugins/sfJobeetPlugin/lib/
ファイルを移動させたので、キャッシュをクリアします:
$ php symfony cc
tip
APCのようなPHPアクセラレーターを利用している場合はこの時点で動作がおかしくなるので、Apacheを再起動させます。
すべてのモデルファイルはプラグインに移動させたので、すべてがきちんと動作するのか確認するためにテストを実行します:
$ php symfony test:all
ControllerとView
次のロジックのステップはモジュールをプラグインに移動させることです。 モジュールの名前の衝突を避けるために、モジュールの名前にプレフィックスとしてプラグインの名前をつけるのは常によい習慣です:
$ mkdir plugins/sfJobeetPlugin/modules/ $ mv apps/frontend/modules/affiliate plugins/sfJobeetPlugin/modules/sfJobeetAffiliate $ mv apps/frontend/modules/api plugins/sfJobeetPlugin/modules/sfJobeetApi $ mv apps/frontend/modules/category plugins/sfJobeetPlugin/modules/sfJobeetCategory $ mv apps/frontend/modules/job plugins/sfJobeetPlugin/modules/sfJobeetJob $ mv apps/frontend/modules/language plugins/sfJobeetPlugin/modules/sfJobeetLanguage
それぞれのモジュールに対して、すべてのactions.class.phpとcomponents.class.phpファイルのクラスの名前を変更することも必要です(たとえば、affiliateActionsクラスはsfJobeetAffiliateActionsにリネームする必要があります)。
次のテンプレートのinclude_partial()とinclude_component()呼び出しも変更しなければなりません:
sfJobeetAffiliate/templates/_form.php(affiliateをsfJobeetAffiliateに変更する)sfJobeetCategory/templates/showSuccess.atom.phpsfJobeetCategory/templates/showSuccess.phpsfJobeetJob/templates/indexSuccess.atom.phpsfJobeetJob/templates/indexSuccess.phpsfJobeetJob/templates/searchSuccess.phpsfJobeetJob/templates/showSuccess.phpapps/frontend/templates/layout.php
searchとdeleteアクションを更新します:
// plugins/sfJobeetPlugin/modules/sfJobeetJob/actions/actions.class.php class sfJobeetJobActions extends sfActions { public function executeSearch(sfWebRequest $request) { if (!$query = $request->getParameter('query')) { return $this->forward('sfJobeetJob', 'index'); } $this->jobs = Doctrine::getTable('JobeetJob') ->getForLuceneQuery($query); if ($request->isXmlHttpRequest()) { if ('*' == $query || !$this->jobs) { return $this->renderText('No results.'); } else { return $this->renderPartial('sfJobeetJob/list', array('jobs' => $this->jobs)); } } } public function executeDelete(sfWebRequest $request) { $request->checkCSRFProtection(); $jobeet_job = $this->getRoute()->getObject(); $jobeet_job->delete(); $this->redirect('sfJobeetJob/index'); } // ... }
最後に、これらの修正が考慮されるようにrouting.ymlファイルを修正します:
# apps/frontend/config/routing.yml
affiliate:
class: sfDoctrineRouteCollection
options:
model: JobeetAffiliate
actions: [new, create]
object_actions: { wait: GET }
prefix_path: /:sf_culture/affiliate
module: sfJobeetAffiliate
requirements:
sf_culture: (?:fr|en)
api_jobs:
url: /api/:token/jobs.:sf_format
class: sfDoctrineRoute
param: { module: sfJobeetApi, action: list }
options: { model: JobeetJob, type: list, method: getForToken }
requirements:
sf_format: (?:xml|json|yaml)
category:
url: /:sf_culture/category/:slug.:sf_format
class: sfDoctrineRoute
param: { module: sfJobeetCategory, action: show, sf_format: html }
options: { model: JobeetCategory, type: object, method: doSelectForSlug }
requirements:
sf_format: (?:html|atom)
sf_culture: (?:fr|en)
job_search:
url: /:sf_culture/search
param: { module: sfJobeetJob, action: search }
requirements:
sf_culture: (?:fr|en)
job:
class: sfDoctrineRouteCollection
options:
model: JobeetJob
column: token
object_actions: { publish: PUT, extend: PUT }
prefix_path: /:sf_culture/job
module: sfJobeetJob
requirements:
token: \w+
sf_culture: (?:fr|en)
job_show_user:
url: /:sf_culture/job/:company_slug/:location_slug/:id/:position_slug
class: sfDoctrineRoute
options:
model: JobeetJob
type: object
method_for_query: retrieveActiveJob
param: { module: sfJobeetJob, action: show }
requirements:
id: \d+
sf_method: GET
sf_culture: (?:fr|en)
change_language:
url: /change_language
param: { module: sfJobeetLanguage, action: changeLanguage }
localized_homepage:
url: /:sf_culture/
param: { module: sfJobeetJob, action: index }
requirements:
sf_culture: (?:fr|en)
homepage:
url: /
param: { module: sfJobeetJob, action: index }
JobeetのWebサイトを見ようとすると、モジュールが有効にされていないことを知らせる例外が表示されます。
プラグインはプロジェクトのすべてのアプリケーションの間で共有されるので、アプリケーションに必要なモジュールをsettings.yml設定ファイルで有効にする必要があります:
# apps/frontend/config/settings.yml
all:
.settings:
enabled_modules:
- default
- sfJobeetAffiliate
- sfJobeetApi
- sfJobeetCategory
- sfJobeetJob
- sfJobeetLanguage
マイグレーションの最後のステップはモジュールの名前に対してテストする機能テストを修正することです。
タスク
タスクはプラグインに簡単に移動させることができます:
$ mv lib/task plugins/sfJobeetPlugin/lib/
国際化ファイル
プラグインはXLIFFファイルも格納できます:
$ mv apps/frontend/i18n plugins/sfJobeetPlugin/
ルーティング
プラグインはルーティングルールも格納できます:
$ mv apps/frontend/config/routing.yml plugins/sfJobeetPlugin/config/
アセット
少し直感に反していますが、プラグインは、画像、スタイルシートとJavaScriptのようなWebアセットも格納できます。
Jobeetプラグインを配布したくないので、本当に意味がありませんが、plugins/sfJobeetPlugin/web/ディレクトリを作ることで実現できます。
プラグインのアセットはブラウザーから閲覧可能なプロジェクトのweb/ディレクトリからアクセスできなければなりません。
plugin:publish-assetsはUnixシステムではシンボリックリンクの作成、Windowsプラットフォームではファイルをコピーすることでこれに対処します:
$ php symfony plugin:publish-assets
ユーザー
求人履歴を扱うmyUserクラスメソッドを移動させることは少し手間がかかります。
JobeetUserクラスを作成しmyUserを継承させることもできます。
しかし、とりわけいくつかのプラグインが新しいメソッドをクラスに追加したい場合、ベターな方法があります。
リスニングできるライフサイクルの間にsymfonyのコアオブジェクトはイベントを通知します。
我々の事例では、未定義のメソッドがsfUserオブジェクトに呼び出されるときに起きるuser.method_not_foundイベントをリスニングする必要があります。
symfonyが初期化されるときにプラグインのコンフィギュレーションクラスがある場合、すべてのプラグインも初期化されます:
// plugins/sfJobeetPlugin/config/sfJobeetPluginConfiguration.class.php class sfJobeetPluginConfiguration extends sfPluginConfiguration { public function initialize() { $this->dispatcher->connect('user.method_not_found', array('JobeetUser', 'methodNotFound')); } }
イベントの通知はイベントディスパッチャーオブジェクトであるsfEventDispatcherによって管理されます。
connect()メソッドを呼び出せばリスナーが登録されます。
connect()メソッドはイベントの名前をPHP callableに結びつけます。
note
PHP callableはPHP変数でcall_user_func()関数によって使われis_callable()関数に渡されるときにtrueを返します。
文字列は関数を表し、配列はオブジェクトメソッドもしくはクラスメソッドを表します。
上記のコードによって、myUserオブジェクトはメソッドを見つけられないときはJobeetUserクラスのmethodNotFound()スタティックメソッドを呼び出します。
見つからないメソッドを処理するもしくは処理しないのはmethodNotFound()メソッド次第です。
myUserクラスからすべてのメソッドを削除しJobeetUserクラスを作成します:
// apps/frontend/lib/myUser.class.php class myUser extends sfBasicSecurityUser { } // plugins/sfJobeetPlugin/lib/JobeetUser.class.php class JobeetUser { static public function methodNotFound(sfEvent $event) { if (method_exists('JobeetUser', $event['method'])) { $event->setReturnValue(call_user_func_array( array('JobeetUser', $event['method']), array_merge(array($event->getSubject()), $event['arguments']) )); return true; } } static public function isFirstRequest(sfUser $user, $boolean = null) { if (is_null($boolean)) { return $user->getAttribute('first_request', true); } else { $user->setAttribute('first_request', $boolean); } } static public function addJobToHistory(sfUser $user, JobeetJob $job) { $ids = $user->getAttribute('job_history', array()); if (!in_array($job->getId(), $ids)) { array_unshift($ids, $job->getId()); $user->setAttribute('job_history', array_slice($ids, 0, 3)); } } static public function getJobHistory(sfUser $user) { $ids = $user->getAttribute('job_history', array()); if (!empty($ids)) { return Doctrine::getTable('JobeetJob') ->createQuery('a') ->whereIn('a.id', $ids) ->execute(); } else { return array(); } } static public function resetJobHistory(sfUser $user) { $user->getAttributeHolder()->remove('job_history'); } }
ディスパッチャがmethodNotFound()メソッドを呼び出すとき、sfEventオブジェクトを渡します。
JobeetUserクラスにメソッドが存在する場合、このメソッドが呼び出され、その後で戻り値は通知元オブジェクトに返されます。
そうではない場合、symfonyは次に登録されたリスナーを試すもしくは例外を投げます。
getSubject()メソッドはイベントの通知元オブジェクトを返します。
この場合現在のmyUserオブジェクトです。
いつものように新しいクラスを作るとき、ブラウザーで見るもしくはテストを実行する前にキャッシュをクリアすることをお忘れなく:
$ php symfony cc
デフォルト構造 vs プラグインアーキテクチャ
プラグインのアーキテクチャを利用することで異なる方法でコードを編成できます:

プラグインを使う
新しい機能の実装を始めるとき、もしくはWebの古典的な問題を解決しようとする場合、おそらく誰かが同じ問題をすでに解決していてsymfonyのプラグインとして解決方法をパッケージにしています。 symfonyの公開プラグインを探すには、symfony公式サイトのプラグインセクションに移動します。
プラグインはディレクトリに内蔵されるので、インストールする方法は複数あります:
plugin:installタスクを使う(プラグインの開発者がプラグインパッケージを作りsymfonyの公式サイトにアップロードする場合のみ機能する)- パッケージをダウンロードして
plugins/ディレクトリの下で手動で解凍する(開発者がパッケージをアップロードすることも必要) plugins/でプラグイン用のsvn:externalsを作る(プラグインの作者がSuversionでプラグインをホストする場合のみ機能する)
後者の2つの方法は簡単ですが柔軟性に欠けます。 最初の方法でプロジェクトのsymfonyのバージョンに合わせて最新バージョンをインストールして、 最新の安定版に簡単にアップグレードする、そして簡単にプラグイン間の依存関係を管理できます。
プラグインを寄付する
プラグインのパッケージを作成する
プラグインパッケージを作成するには、必須のファイルをプラグインのディレクトリ構造に追加する必要があります。
最初に、プラグインのルートディレクトリでREADMEファイルを作りプラグインのインストール方法、これが何を提供するもの、しないものを記述します。
READMEファイルはMarkdownフォーマットでフォーマットしなければなりません。
このファイルはsymfony公式サイトでドキュメントのメインピースとして使われます。
symfony plugin dingusを利用してREADMEファイルをHTMLに変換することができます。
LICENSEファイルを作ることも必要です。
ライセンスの選択は簡単な作業ではありませんが、symfonyのプラグインセクションはsymfonyのライセンス(MIT、BSD、LGPL、とPHP)と似たライセンスでリリースされたプラグインのみを表示します。
LICENSEファイルの内容はプラグインの公開ページのlicenseタブの下で表示されます。
最後のステップはプラグインディレクトリのルートでpackage.xmlファイルを作ることです。
このpackage.xmlファイルはPEARパッケージ構文に従います。
note
package.xmlの構文を学ぶベストな方法は既存のプラグインで使われるファイルをコピーすることです。
package.xmlファイルはいくつかの部分で構成されます。
このテンプレートの例は次のようなものです:
<!-- plugins/sfJobeetPlugin/package.xml --> <?xml version="1.0" encoding="UTF-8"?> <package packagerversion="1.4.1" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd" > <name>sfJobeetPlugin</name> <channel>plugins.symfony-project.org</channel> <summary>A job board plugin.</summary> <description>A job board plugin.</description> <lead> <name>Fabien POTENCIER</name> <user>fabpot</user> <email>fabien.potencier@symfony-project.com</email> <active>yes</active> </lead> <date>2008-12-20</date> <version> <release>1.0.0</release> <api>1.0.0</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <license uri="http://www.symfony-project.com/license"> MIT license </license> <notes /> <contents> <!-- CONTENT --> </contents> <dependencies> <!-- DEPENDENCIES --> </dependencies> <phprelease> </phprelease> <changelog> <!-- CHANGELOG --> </changelog> </package>
<contents>タグはパッケージに設置する必要のあるファイルを含みます:
<contents> <dir name="/"> <file role="data" name="README" /> <file role="data" name="LICENSE" /> <dir name="config"> <file role="data" name="config.php" /> <file role="data" name="schema.yml" /> </dir> <!-- ... --> </dir> </contents>
<dependencies>タグはプラグインが持つすべての依存関係の参照: PHP、symfony、と他のプラグインをつけます。
この情報はプロジェクト環境に対してベストなプラグインのバージョンをインストールし存在するのであれば必要なプラグインの依存関係をインストールするためにplugin:installタスクによって使われます。
<dependencies> <required> <php> <min>5.0.0</min> </php> <pearinstaller> <min>1.4.1</min> </pearinstaller> <package> <name>symfony</name> <channel>pear.symfony-project.com</channel> <min>1.2.0</min> <max>1.3.0</max> <exclude>1.3.0</exclude> </package> </required> </dependencies>
symfonyはバージョンによって微妙に異なるAPIを持つので、ここで行ったように、依存関係を常に宣言すべきです。
最大バージョンと最小バージョンを宣言することでplugin:installはsymfonyの必須バージョンがわかります。
別のプラグインとの依存関係を宣言することも可能です:
<package> <name>sfFooPlugin</name> <channel>plugins.symfony-project.org</channel> <min>1.0.0</min> <max>1.2.0</max> <exclude>1.2.0</exclude> </package>
<changelog>タグはオプションですがリリースの間の変更に関する有益な情報が得られます。
この情報は"Changelog"タブとプラグインのフィードで見ることができます。
<changelog> <release> <version> <release>1.0.0</release> <api>1.0.0</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <license uri="http://www.symfony-project.com/license"> MIT license </license> <date>2008-12-20</date> <license>MIT</license> <notes> * fabien: First release of the plugin </notes> </release> </changelog>
symfony公式サイトでプラグインを公開する
便利なプラグインを開発してsymfonyのコミュニティと共有したい場合、まだなければsymfony公式サイトのアカウントを作成し、新しいプラグインを作成します。
あなたは自動的にプラグインの管理者になりインターフェイスの"admin"タブを見ることになります。 このタブにおいて、プラグインを管理してパッケージをアップロードするために必要なすべての機能が見つかります。
note
プラグインのFAQページにはプラグイン開発者のための有益な情報がたくさんあります。
また明日
プラグインの作成とコミュニティとの共有はsymfony公式サイトへのベストな貢献方法の1つです。 これはとても簡単なので、symfonyのプラグインリポジトリは便利で面白く、しかしおかしなプラグインで満たされています。
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.