TRAJOIN is an Application to Translate symfony documents Jointly.

home > 1.2/book > 17-Extending-Symfony.txt

[1] Edit ↑TOP

第17章 - symfonyを拡張する


[2] Edit ↑TOP

Eventually, you will need to alter symfony's behavior. Whether you need to modify the way a certain class behaves or add your own custom features, the moment will inevitably happen becase all clients have specific requirements that no framework can forecast. Actually, this situation is so common that symfony provides a mechanism to extend existing classes at runtime, beyond simple class inheritance. You can even replace the core symfony classes by modifying the factories settings. Once you have built an extension, you can easily package it as a plug-in, so that it can be reused in other applications, or by other symfony users.


[3] Edit ↑TOP

イベント


[4] Edit ↑TOP

PHP does not support multiple inheritence, which means it is not possible to have a class extend more than one other class. Also it is not possible to add new methods to an existing class or override existing methods. To ease these two limitations and to make the framework truly extendable, symfony introduces an event system, inspired by the Cocoa notification center, and based on the Observer design pattern (http://en.wikipedia.org/wiki/Observer_pattern).


[5] Edit ↑TOP

イベントを理解する


[6] Edit ↑TOP

Some of the symfony classes "notify the dispatcher of an event" at various moments of their life. For instance, when the user changes their culture, the user object notifies that a change_culture event has occurred. This is like a shout in the project's space, saying: "I'm doing that. Do whatever you want about it".


[7] Edit ↑TOP

You can decide to do something special when an event is fired. For instance, you could save the user culture to a database table each time the change_culture event occurrs. In order to do so, you need to register an event listener, in other words you must declare a function that will be called when the event occurs. Listing 17-1 shows how to register a listener on the user's change_culture event.


[8] Edit ↑TOP

リスト 17-1 - イベントリスナを登録する


[9] Edit ↑TOP

$dispatcher->connect('user.change_culture', 'changeUserCulture');

function changeUserCulture(sfEvent $event)
{
  $user = $event->getSubject();
  $culture = $event['culture'];

  // ユーザのcultureで何かを行う
}

[10] Edit ↑TOP

All events and listener registrations are managed by a special object called the event dispatcher. This object is available from everywhere in symfony by way of the sfContext singleton, and most symfony objects offer a getEventDispatcher() method to get direct access to it. Using the dispatcher's connect() method, you can register any PHP callable (either a class method or a function) to be called when an event occurs. The first argument of connect() is the event identifier, which is a string composed of a namespace and a name. The second argument is a PHP callable.


[11] Edit ↑TOP
Retrieving the event dispatcher from anywhere in the application:


$dispatcher = sfContext::getInstance()->getEventDispatcher();

[12] Edit ↑TOP

Once the function is registered with the event dispatcher, it waits until the event is fired. The event dispatcher keeps a record of all event listeners, and knows which ones to call when an event occurs. When calling these methods or functions, the dispatcher passes them an sfEvent object as a parameter.


[13] Edit ↑TOP

The event object stores information about the notified event. The event notifier can be retrieved thanks to the getSubject() method, and the event parameters are accessible by using the event object as an array (for example, $event['culture'] can be used to retrieve the culture parameter passed by sfUser when notifying user.change_culture).


[14] Edit ↑TOP

To wrap up, the event system allows you to add abilities to an existing class or modify its methods at runtime, without using inheritance.


[15] Edit ↑TOP
: In version 1.0, symfony used a similar system but with a different syntax. You may see calls to static methods of an sfMixer class to register and notify events in symfony 1.0 code, instead of calls to methods of the event dispatcher. sfMixer calls are deprecated, yet they still work in symfony 1.2.


[16] Edit ↑TOP

Notifying an Event listener


[17] Edit ↑TOP

Just like symfony classes notify that events have occurred, your own classes can offer runtime extensibility and notify of events at certain occasions. For instance, let's say that your application requests several third-party web services, and that you have written an sfRestRequest class to wrap the REST logic of these requests. A good idea would be to trigger an event each time this class makes a new request. This would make the addition of logging or caching capabilities easier in the future. Listing 17-2 shows the code you need to add to an existing fetch() method to make it notify an event listener.


[18] Edit ↑TOP

Listing 17-2 - Notifying an Event listener


[19] Edit ↑TOP

class sfRestRequest
{
  protected $dispatcher = null;

  public function __construct(sfEventDispatcher $dispatcher)
  {
    $this->dispatcher = $dispatcher;
  }

  /**
   * Makes a query to an external web service
   */
  public function fetch($uri, $parameters = array())
  {
    // Notify the dispatcher of the beginning of the fetch process
    $this->dispatcher->notify(new sfEvent($this, 'rest_request.fetch_prepare', array(
      'uri'        => $uri,
      'parameters' => $parameters
    )));

    // Make the request and store the result in a $result variable
    // ...

    // Notify the dispatcher of the end of the fetch process
    $this->dispatcher->notify(new sfEvent($this, 'rest_request.fetch_success', array(
      'uri'        => $uri,
      'parameters' => $parameters,
      'result'     => $result
    )));

    return $result;
  }
}

[20] Edit ↑TOP

The notify() method of the event dispatcher expects an sfEvent object as an argument; this is the very same object that is passed to the event listeners. This object always carries a reference to the notifier (that's why the event instance is initialized with this) and an event identifier. Optionally, it accepts an associative array of parameters, giving listeners a way to interact with the notifier's logic.


[21] Edit ↑TOP
Only the classes that notify events can be extended by way of the event system. So even if you don't know whether you will need to extend a class in the future or not, it is always a good idea to add notifications in the key methods.


[22] Edit ↑TOP

Notifying the dispatcher of an Event Until a Listener handles it


[23] Edit ↑TOP

By using the notify() method, you make sure that all the listeners registered on the notifying event are executed. However, in some cases you need to allow a listener to stop the event and prevent further listeners from being notified about it. In this case, you should use notifyUntil() instead of notify(). The dispatcher will then execute all listeners until one returns true, and then stop the event notification. In other words, notifyUntil() is like a shout in the project space saying: "I'm doing that. If somebody cares, then I won't tell anybody else". Listing 17-3 shows how to use this technique in combination with a magic __call() method to add methods to an existing class at runtime.


[24] Edit ↑TOP

Listing 17-3 - Notifying of an Event Until a Listener Returns True


[25] Edit ↑TOP

class sfRestRequest
{
  // ...

  public function __call($method, $arguments)
  {
    $event = $this->dispatcher->notifyUntil(new sfEvent($this, 'rest_request.method_not_found', array(
      'method'    => $method, 
      'arguments' => $arguments
    )));
    if (!$event->isProcessed())
    {
      throw new sfException(sprintf('Call to undefined method %s::%s.', get_class($this), $method));
    }

    return $event->getReturnValue();
  }
}

[26] Edit ↑TOP

An event listener registered on the rest_request.method_not_found event can test the requested $method and decide to handle it, or pass to the next event listener callable. In Listing 17-4, you can see how a third party class can add put() and delete() methods to the sfRestRequest class at runtime with this trick.


[27] Edit ↑TOP

Listing 17-4 - Handling a "Notify Until" Event type


[28] Edit ↑TOP

class frontendConfiguration extends sfApplicationConfiguration
{
  public function configure()
  {
    // ...

    // リスナを登録する
    $this->dispatcher->connect('rest_request.method_not_found', array('sfRestRequestExtension', 'listenToMethodNotFound'));
  }
}

class sfRestRequestExtension
{
  static public function listenToMethodNotFound(sfEvent $event)
  {
    switch ($event['method'])
    {
      case 'put':
        self::put($event->getSubject(), $event['arguments'])

        return true;
      case 'delete':
        self::delete($event->getSubject(), $event['arguments'])

        return true;
      default:
        return false;
    }
  }

  static protected function put($restRequest, $arguments)
  {
    // putリクエストを行い結果を変数$resultに保存する
    // ...

    $event->setReturnValue($result);
  }

  static protected function delete($restRequest, $arguments)
  {
    // deleteリクエストを行い結果を変数$resultに保存する
    // ...

    $event->setReturnValue($result);
  }
}

[29] Edit ↑TOP

In practice, notifyUntil() offers multiple inheritance capabilities, or rather mixins (the addition of methods from third-party classes to an existing class), to PHP. You can now "inject" new methods to objects that you can't extend by way of inheritance. And this happens at runtime. You are not limited by the Object Oriented capabilities of PHP anymore when you use symfony.


[30] Edit ↑TOP
As the first listener to catch a notifyUntil() event prevents further notifications, you may worry about the order in which listeners are executed. This order corresponds to the order in which listeners were registered - first registered, first executed. In practice, cases where this could be an issue seldom happen. If you realize that two listeners conflict on a particular event, perhaps your class should notify several events, for instance one at the beginning and one at the end of the method execution. And if you use events to add new methods to an existing class, name your methods wisely so that other attempts at adding methods don't conflict. Prefixing method names with the name of the listener class is a good practice.


[31] Edit ↑TOP

メソッドの戻り値を変更する


[32] Edit ↑TOP

You can probably imagine how a listener can not only use the information given by an event, but also modify it, to alter the original logic of the notifier. If you want to allow this, you should use the filter() method of the event dispatcher rather than notify(). All event listeners are then called with two parameters: the event object, and the value to filter. Event listeners must return the value, whether they altered it or not. Listing 17-5 shows how filter() can be used to filter a response from a web service and escape special characters in that response.


[33] Edit ↑TOP

Listing 17-5 - Notifying of and Handling a Filter Event


[34] Edit ↑TOP

class sfRestRequest
{
  // ...

  /**
   * Make a query to an external web service
   */
  public function fetch($uri, $parameters = array())
  {
    // Make the request and store the result in a $result variable
    // ...

    // Notify of the end of the fetch process
    return $this->dispatcher->filter(new sfEvent($this, 'rest_request.filter_result', array(
      'uri'        => $uri,
      'parameters' => $parameters,
    )), $result)->getReturnValue();
  }
}

// Add escaping to the web service response
$dispatcher->connect('rest_request.filter_result', 'rest_htmlspecialchars');

function rest_htmlspecialchars(sfEvent $event, $result)
{
  return htmlspecialchars($result, ENT_QUOTES, 'UTF-8');
}

[35] Edit ↑TOP

組み込みのイベント


[36] Edit ↑TOP

Many of symfony's classes have built-in events, allowing you to extend the framework without necessarily changing the classes themselves. Table 17-1 lists these events, together with their types and arguments.


[37] Edit ↑TOP

テーブル17-1 - symfonyのイベント


[38] Edit ↑TOP
イベントの名前空間 イベントの名前 通知者 引数
アプリケーション log notify たくさんのクラス priority
throw_exception notifyUntil sfException -
コマンド log notify sfCommand* classes priority
pre_command notifyUntil sfTask arguments, options
post_command notify sfTask -
filter_options filter sfTask command_manager
設定 method_not_found notifyUntil sfProjectConfiguration method, arguments
コンポーネント method_not_found notifyUntil sfComponent method, arguments
内容 load_factories notify sfContext -
コントローラ change_action notify sfController module, action
method_not_found notifyUntil sfController method, arguments
page_not_found notify sfController module, action
プラグイン pre_install notify sfPluginManager channel, plugin, is_package
post_install notify sfPluginManager channel, plugin
pre_uninstall notify sfPluginManager channel, plugin
post_uninstall notify sfPluginManager channel, plugin
リクエスト filter_parameters filter sfWebRequest path_info
method_not_found notifyUntil sfRequest method, arguments
レスポンス method_not_found notifyUntil sfResponse method, arguments
filter_content filter sfResponse -
ルーティング load_configuration notify sfRouting -
タスク cache.clear notifyUntil sfCacheClearTask app, type, env
テンプレート filter_parameters filter sfViewParameterHolder -
ユーザ change_culture notify sfUser culture
method_not_found notifyUntil sfUser method, arguments
change_authentication notify sfBasicSecurityUser authenticated
ビュー configure_format notify sfView format, response, request
method_not_found notifyUntil sfView method, arguments
ビュー.キャッシュ filter_content filter sfViewCacheManager response, uri, new

[39] Edit ↑TOP

You are free to register event listeners on any of these events. Just make sure that listener callables return a boolean when registered on a notifyUntil event type, and that they return the filtered value when registered on a filter event type.


[40] Edit ↑TOP

Note that the event namespaces don't necessarily match the class role. For instance, all symfony classes notify of an application.log event when they need something to appear in the log files (and in the web debug toolbar):


[41] Edit ↑TOP

$dispatcher->notify(new sfEvent($this, 'application.log', array($message)));

[42] Edit ↑TOP

Your own classes can do the same and also notify symfony events when it makes sense to do so.


[43] Edit ↑TOP

リスナを登録する場所は?


[44] Edit ↑TOP

Event listeners need to be registered early in the life of a symfony request. In practice, the right place to register event listeners is in the application configuration class. This class has a reference to the event dispatcher that you can use in the configure() method. Listing 17-6 shows how to register a listener on one of the rest_request events of the above examples.


[45] Edit ↑TOP

Listing 17-6 - Registering a Listener in the Application Configuration Class, in apps/frontend/config/ApplicationConfiguration.class.php


[46] Edit ↑TOP

class frontendConfiguration extends sfApplicationConfiguration
{
  public function configure()
  {
    $this->dispatcher->connect('rest_request.method_not_found', array('sfRestRequestExtension', 'listenToMethodNotFound'));
  }
}

[47] Edit ↑TOP

Plug-ins (see below) can register their own event listeners. They should do it in the plug-in's config/config.php script, which is executed during application initialization and offers access to the event dispatcher through $this->dispatcher.


[48] Edit ↑TOP

[49] Edit ↑TOP

ファクトリ


[50] Edit ↑TOP

ファクトリ(factory)は特定のタスクのためのクラスの定義です。symfonyはコントローラやセッション機能などのコア機能についてファクトリに依存します。例えば、symfonyが新しいリクエストオブジェクトを作成する必要がある場合、symfonyはこの目的のために使用するクラスの名前のためのファクトリの定義を検索します。リクエスト用のデフォルトのファクトリの定義はsfWebRequestクラスで、symfonyはリクエストを処理するためにこのクラスのオブジェクトを作成します。ファクトリの定義の利点はsymfonyのコア機能をとても簡単に変更できることです: ファクトリの定義を変更し、symfonyは自身の代わりにカスタムのリクエストクラスを使用します。


[51] Edit ↑TOP

ファクトリの定義はfactories.yml設定ファイルに保存されます。リスト17-7はデフォルトのファクトリの定義ファイルを示します。それぞれの定義はオートロードされるクラスの名前と(オプションとして)パラメータの一式から構成されます。例えば、セッションストレージのファクトリ(storage:キーの下で設定)は一貫したセッションを可能にするためにクライアントコンピュータ上で作成されたクッキーに名前を付けるsession_nameパラメータを使用します。


[52] Edit ↑TOP

リスト17-7 - frontend/config/factories.ymlの中の、デフォルトのファクトリファイル


[53] Edit ↑TOP
-
prod:
  logger:
    class:   sfNoLogger
    param:
      level:   err
      loggers: ~

cli:
  controller:
    class: sfConsoleController
  request:
    class: sfConsoleRequest
  response:
    class: sfConsoleResponse

test:
  storage:
    class: sfSessionTestStorage
    param:
      session_path: %SF_TEST_CACHE_DIR%/sessions

  response:
    class: sfWebResponse
    param:
      send_http_headers: false

all:
  routing:
    class: sfPatternRouting
    param:
      generate_shortest_url:            true
      extra_parameters_as_query_string: true

#all:
#  controller:
#    class: sfFrontWebController
#
#  request:
#    class: sfWebRequest
#    param:
#      logging:           %SF_LOGGING_ENABLED%
#      path_info_array:   SERVER
#      path_info_key:     PATH_INFO
#      relative_url_root: ~
#      formats:
#        txt:  text/plain
#        js:   [application/javascript, application/x-javascript, text/javascript]
#        css:  text/css
#        json: [application/json, application/x-json]
#        xml:  [text/xml, application/xml, application/x-xml]
#        rdf:  application/rdf+xml
#        atom: application/atom+xml
#
#  response:
#    class: sfWebResponse
#    param:
#      logging:           %SF_LOGGING_ENABLED%
#      charset:           %SF_CHARSET%
#      send_http_headers: true
#
#  user:
#    class: myUser
#    param:
#      timeout:         1800
#      logging:         %SF_LOGGING_ENABLED%
#      use_flash:       true
#      default_culture: %SF_DEFAULT_CULTURE%
#
#  storage:
#    class: sfSessionStorage
#    param:
#      session_name: symfony
#
#  view_cache:
#    class: sfFileCache
#    param:
#      automatic_cleaning_factor: 0
#      cache_dir:                 %SF_TEMPLATE_CACHE_DIR%
#      lifetime:                  86400
#      prefix:                    %SF_APP_DIR%/template
#
#  i18n:
#    class: sfI18N
#    param:
#      source:               XLIFF
#      debug:                off
#      untranslated_prefix:  "[T]"
#      untranslated_suffix:  "[/T]"
#      cache:
#        class: sfFileCache
#        param:
#          automatic_cleaning_factor: 0
#          cache_dir:                 %SF_I18N_CACHE_DIR%
#          lifetime:                  31556926
#          prefix:                    %SF_APP_DIR%/i18n
#
#  routing:
#    class: sfPatternRouting
#    param:
#      load_configuration:               true
#      suffix:                           ''
#      default_module:                   default
#      default_action:                   index
#      debug:                            %SF_DEBUG%
#      logging:                          %SF_LOGGING_ENABLED%
#      generate_shortest_url:            false
#      extra_parameters_as_query_string: false
#      cache:
#        class: sfFileCache
#        param:
#          automatic_cleaning_factor: 0
#          cache_dir:                 %SF_CONFIG_CACHE_DIR%/routing
#          lifetime:                  31556926
#          prefix:                    %SF_APP_DIR%/routing
#
#  logger:
#    class: sfAggregateLogger
#    param:
#      level: debug
#      loggers:
#        sf_web_debug:
#          class: sfWebDebugLogger
#          param:
#            level: debug
#            condition:       %SF_WEB_DEBUG%
#            xdebug_logging:  true
#            web_debug_class: sfWebDebug
#        sf_file_debug:
#          class: sfFileLogger
#          param:
#            level: debug
#            file: %SF_LOG_DIR%/%SF_APP%_%SF_ENVIRONMENT%.log

[55] Edit ↑TOP

ファクトリを変更する最良の方法はデフォルトのファクトリから継承した新しいクラスを作り、新しいメソッドをそのクラスに追加することです。例えば、ユーザセッションのファクトリはmyUserクラス(frontend/lib/ディレクトリに設置)に設定され、sfUserクラスから継承します。既存のファクトリを利用するには同じメカニズムを使います。リスト17-8はリクエストオブジェクトのための新しいファクトリの例を示しています。


[56] Edit ↑TOP

リスト17-8 - ファクトリをオーバーライドする


[57] Edit ↑TOP

//オートロードされたディレクトリ内でmyRequest.class.phpを作る
// 例えばfrontend/lib/において
<?php

class myRequest extends sfRequest
{
  // あなたのコードをここに
}

// factories.ymlでクラスをリクエストファクトリとして宣言する
all:
  request:
    class: myRequest

[58] Edit ↑TOP

他のフレームワークのコンポーネントと統合する


[59] Edit ↑TOP

サードパーティのクラスによって提供された機能が必要な場合、このクラスをlib/ディレクトリの1つにコピーしたくない場合、おそらくはsymfonyがファイルを探す通常の場所の外側にそのクラスをインストールすることになります。この場合、クラスを利用するには、オートロードを利用するsymfonyのsplオートロード統合機能を使わない限り、requreステートメントを手動でコードに含めることになります。


[60] Edit ↑TOP

symfonyは(まだ)すべてのためのツールを提供していません。PDFジェネレータ、Google MapsのAPI、PHPによるLucene検索エンジンの実装など、おそらくZend Frameworkからいくつかのライブラリが必要になります。PHPで直接イメージを操作する、Eメールを読むためにPOP3アカウントに接続する、コンソールのインタフェースを設計することなどを行いたい場合、eZcomponentsからライブラリを選ぶことがあるかもしれません。幸いにして、正しい設定を定義すると、これらのライブラリからのコンポーネントはsymfonyで正常に動作します。


[61] Edit ↑TOP

(PEAR経由でサードパーティのライブラリをインストールしない限り)最初に、アプリケーションのapp.ymlファイルの中でライブラリのrootディレクトリへのパスを宣言する必要があります:


[62] Edit ↑TOP
all:
  zend_lib_dir:   /usr/local/zend/library/
  ez_lib_dir:     /usr/local/ezcomponents/
  swift_lib_dir:  /usr/local/swiftmailer/

[63] Edit ↑TOP

それから、symfonyがオートロードを失敗するとき、考慮するライブラリを指定することでPHPのオートロードシステムを拡張します。リスト17-9のように、オートロードクラスをアプリケーションの設定クラスに登録することでこれを行うことができます(詳細は19章を参照)。


[64] Edit ↑TOP

リスト17-9 - apps/frontend/config/ApplicationConfiguration.class.phpの中で、サードパーティのコンポーネントのオートロードを有効にする


[65] Edit ↑TOP

class frontendConfiguration extends sfApplicationConfiguration
{
  public function initialize()
  {
    parent::initialize(); // 最初にsymfonyのオートロード機能をロードする

    // Zend Frameworkを統合する
    if ($sf_zend_lib_dir = sfConfig::get('app_zend_lib_dir'))
    {
      set_include_path($sf_zend_lib_dir.PATH_SEPARATOR.get_include_path());
      require_once($sf_zend_lib_dir.'/Zend/Loader.php');
      spl_autoload_register(array('Zend_Loader', 'autoload'));
    }

    // eZ Componentsを統合する
    if ($sf_ez_lib_dir = sfConfig::get('app_ez_lib_dir'))
    {
      set_include_path($sf_ez_lib_dir.PATH_SEPARATOR.get_include_path());
      require_once($sf_ez_lib_dir.'/Base/base.php');
      spl_autoload_register(array('ezcBase', 'autoload'));
    }

    // Swift Mailerを統合する
    if ($sf_swift_lib_dir = sfConfig::get('app_swift_lib_dir'))
    {
      set_include_path($sf_swift_lib_dir.PATH_SEPARATOR.get_include_path());
      require_once($sf_swift_lib_dir.'/Swift/ClassLoader.php');
      spl_autoload_register(array('Swift_ClassLoader', 'load'));
    }
  }
}

[66] Edit ↑TOP

ロードされていないクラスの新しいオブジェクトを作るときに起きることは単純です:


[67] Edit ↑TOP
  1. symfonyのオートロード機能は最初にautoload.ymlファイルで宣言されたパスの中でクラスを探します。
  2. クラスパスが見つからなければ、spl_autoload_register()によって登録されたコールバックメソッドはそれらの1つがtrueを返すまで次から次へと呼び出されます。そしてZend_Loader::autoload()ezcBase::autoload()、とSwift_ClassLoader::load()のうちの1つがクラスを見つけるまでこれらが呼び出されます。
  3. これらもfalseを返す場合、PHPはエラーを生成します。

[68] Edit ↑TOP

このことは他のフレームワークコンポーネントはオートロードメカニズムから恩恵を受け、独自環境よりもこれらを簡単に使用できることを意味します。例えば、Lucene検索エンジンと同等のものをPHPで実装するためにZend FrameworkのZend_Searchコンポーネントを使いたい場合、通常はrequireステートメントが必要です:


[69] Edit ↑TOP

require_once 'Zend/Search/Lucene.php';
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
// ...

[70] Edit ↑TOP

symfonyとsplのオートロード機能によって、上記のコードはよりシンプルになります。requireステートメントを省略できるのでincludeのパスとクラスの位置に悩まなくて済みます:


[71] Edit ↑TOP

$doc = new Zend_Search_Lucene_Document(); // クラスがオートロードされた
$doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
// ...

[72] Edit ↑TOP

プラグイン


[73] Edit ↑TOP

1つのsymfonyアプリケーションのために開発したコードピースの再利用がおそらく必要になります。このコードのピースを単独のクラスのパッケージにすることができるのであれば、問題ありません: クラスを別のアプリケーションのlib/フォルダの1つに設置すればオートローダが残りを引き受けます。しかし、コードが複数のファイルに散在している場合、例えば、administrationジェネレータ用の完全に新しいテーマ、もしくは好みの視覚効果を自動化するJavaScriptファイルとヘルパの組み合わせなどの場合、ファイルをコピーするだけの方法は最良の解決方法ではありません。


[74] Edit ↑TOP

プラグインはいくつかのファイルにまたがるコードをパッケージにする方法と、いくつかのプロジェクトをまたがってこのコードを再利用する方法を提供します。プラグインの中で、クラス、フィルタ、イベントリスナ、ヘルパ、設定、タスク、モジュール、スキーマ、モデルの拡張、フィクスチャ、ウェブアセットなどをパッケージにすることができます。プラグインをインストール、アップグレード、アンインストールする方法は簡単です。これらは.tgzアーカイブ、PEARパッケージ、もしくはコードリポジトリとして配布可能で、コードのリポジトリから簡単にチェックアウトできます。PEARパッケージとなったプラグインは依存関係の管理機能を利用し、アップグレードと自動検出が簡単です。symfonyのロードメカニズムはプラグインを考慮し、プラグインによって提供される機能はあたかもフレームワークの一部であるかのようにプロジェクトで利用できます。


[75] Edit ↑TOP

ですので、プラグインは基本的にsymfonyプロジェクトのために拡張機能をパッケージにしたものです。プラグインによってアプリケーションを越えて独自コードを再利用できるだけでなく、別の投稿者によって開発されたものも再利用可能でsymfonyコアにサードパーティの拡張機能を追加できます。


[76] Edit ↑TOP

symfonyのプラグインを見つける


[77] Edit ↑TOP

symfonyの公式サイトにはプラグイン専用のページが存在しており、次のURLからアクセスできます:


[78] Edit ↑TOP
http://www.symfony-project.org/plugins/

[79] Edit ↑TOP

ここに掲載されている各プラグインはそれぞれのページでインストールについての説明やドキュメントが整備されています。


[80] Edit ↑TOP

ここにあるプラグインはコミュニティから寄せられたものもあれば、symfonyのコア開発者が開発したものもあります。後者のものについては以下を参照してください。:


[81] Edit ↑TOP
  • sfFeed2Plugin: Automates the manipulation of RSS and Atom feeds
  • sfThumbnailPlugin: Creates thumbnails--for instance, for uploaded images
  • sfMediaLibraryPlugin: Allows media upload and management, including an extension for rich text editors to allow authoring of images inside rich text
  • sfShoppingCartPlugin: Allows shopping cart management
  • sfGuardPlugin: Provides authentication, authorization, and other user management features above the standard security feature of symfony
  • sfPrototypePlugin: Provides prototype and script.aculo.us JavaScript files as a standalone library
  • sfSuperCachePlugin: Writes pages in cache directory under the web root to allow the web server to serve them as fast as possible
  • sfOptimizerPlugin: Optimizes your application's code to make it execute faster in the production environment (see the next chapter for details)
  • sfErrorLoggerPlugin: Logs every 404 and 500 error in a database and provides an administration module to browse these errors
  • sfSslRequirementPlugin: Provides SSL encryption support for actions

[82] Edit ↑TOP

公式サイトはビヘイビア(behavior)と呼ばれる、Propelオブジェクトを拡張するために設計されたプラグインも提示します。これらの中で、次のものが見つかります:


[83] Edit ↑TOP
  • sfPropelParanoidBehaviorPlugin: オブジェクトの削除を無効にして、deleted_atカラムの更新で置き換える
  • sfPropelOptimisticLockBehaviorPlugin: Propelオブジェクト用にオプティミスティックロック(楽観的ロック)を実装する

[84] Edit ↑TOP

公式サイトの専用ページを定期的に確認すべきです。いつも新しいプラグインが追加され、これらはウェブアプリケーションのプログラミングの多くの面にとても便利なショートカットをもたらしてくれます。


[85] Edit ↑TOP

公式サイトの専用ページは別にして、プラグインを配布する他の方法はダウンロードのためのプラグインアーカイブを提供することと、PEARチャネルでプラグインをホストすること、もしくは公開のバージョンコントロールリポジトリに保存することです。


[86] Edit ↑TOP

プラグインをインストールする


[87] Edit ↑TOP

プラグインのインストール作業はプラグインパッケージの作成方法によって異なります。常にREADMEファイルかつ・もしくはプラグインのダウンロードのページ上のインストールの手引きを参照して下さい。


[88] Edit ↑TOP

プラグインはプロジェクト単位でインストールされます。次のセクションで説明されるすべての方法ではすべてのプラグインのファイルをmyproject/plugins/pluginName/ディレクトリに設置します。


[89] Edit ↑TOP

PEARプラグイン


[90] Edit ↑TOP

公式サイトの専用ページの一覧に記載されているプラグインはPEARチャネル: plugins:symfony-project.orgを通して入手できます。プラグインをインストールするためには、リスト17-10で示されるように、プラグインの名前と一緒にplugin:installタスクを使います。


[91] Edit ↑TOP

リスト17-10 - 公式サイトのPEARチャネルからプラグインをインストールする


[92] Edit ↑TOP
> cd myproject
> php symfony plugin:install pluginName

[93] Edit ↑TOP

代わりの方法として、プラグインをダウンロードしてディスクからインストールすることもできます。この場合、リスト17-11で示されるように、パッケージアーカイブへのパスを使います。


[94] Edit ↑TOP

リスト17-11 - ダウンロードしたパッケージからプラグインをインストールする


[95] Edit ↑TOP
> cd myproject
> php symfony plugin:install /home/path/to/downloads/pluginName.tgz

[96] Edit ↑TOP

プラグインの中には外部のPEARチャネルでホストされるものがあります。リスト17-12で示されるように、plugin:installタスクでそれらをインストールしてチャネルを登録してチャネルの名前を記載することを忘れないで下さい。


[97] Edit ↑TOP

リスト17-12 - PEARチャネルからプラグインをインストールする


[98] Edit ↑TOP
> cd myproject
> php symfony plugin:add-channel channel.symfony.pear.example.com
> php symfony plugin:install --channel=channel.symfony.pear.example.com pluginName

[99] Edit ↑TOP

これら3つのタイプのインストール方法はすべてPEARパッケージを使うので、"PEARプラグイン"という用語はsymfonyプラグインのPEARチャネル、外部のPEARチャネル、もしくはダウンロードしたPEARパッケージからインストールしたプラグインを区別無く説明するために使われます。


[100] Edit ↑TOP

リスト17-13で示されているように、plugin:installタスクは多くのオプションを取ります。


[101] Edit ↑TOP

リスト17-13 - いくつかのオプションを付けてプラグインをインストールする


[102] Edit ↑TOP
> php symfony plugin:install --stability=beta pluginName
> php symfony plugin:install --release=1.0.3 pluginName
> php symfony plugin:install --install-deps pluginName

[103] Edit ↑TOP
すべてのsymfonyタスクに関しては、php symfony help plugin:installを実行すればplugin:installのオプションと引数の説明を見ることができます


[104] Edit ↑TOP

アーカイブのプラグイン


[105] Edit ↑TOP

Some plug-ins come as a simple archive of files. To install those, just unpack the archive into your project's plugins/ directory. If the plug-in contains a web/ subdirectory, don't forget to run the plugin:publish-assets command to create the corresponding symlink under the main web/ folder as shown in listing 17-14. Finally, don't forget to clear the cache.


[106] Edit ↑TOP

リスト17-14 - アーカイブからプラグインをインストールする


[107] Edit ↑TOP
> cd plugins
> tar -zxpf myPlugin.tgz
> cd ..
> php symfony plugin:publish-assets
> php symfony cc

[108] Edit ↑TOP

バージョン管理システムのリポジトリからプラグインをインストールする


[109] Edit ↑TOP

プラグインはときにバージョン管理システム用の独自のソースコードリポジトリを持つことがあります。plugins/ディレクトリの中でチェックアウトするだけでこれらのプラグインをインストールできますが、プロジェクト自身がバージョン管理システムの管理下にある場合、この作業によって問題が引き起こされる可能性があります。


[110] Edit ↑TOP

代わりの方法として、プラグインを外部依存のライブラリとして宣言することが可能で、すべてのプロジェクトのソースコードを更新するとプラグインのソースコードも更新されます。例えば、Subversionはsvn:externalsプロパティで外部依存を保存します。ですので、リスト17-15で示されているように、このプロパティを編集してソースコードを後で更新することでプラグインを追加できます。


[111] Edit ↑TOP

リスト17-15 - ソースのバージョン管理リポジトリからプラグインをインストールする


[112] Edit ↑TOP
> cd myproject
> svn propedit svn:externals plugins
  pluginName   http://svn.example.com/pluginName/trunk
> svn up
> php symfony plugin:publish-assets
> php symfony cc

[113] Edit ↑TOP
If the plug-in contains a web/ directory, the symfony plugin:publish-assets command has to be run to generate the corresponding symlink under the main web/ folder of the project.


[114] Edit ↑TOP

プラグインモジュールを有効にする


[115] Edit ↑TOP

プラグインの中にはモジュール全体を含むものがあります。プラグインモジュールと古典的なモジュールの違いはプラグインモジュールがmyproject/frontend/modules/ディレクトリに現れないことだけです(簡単にアップグレードできる状態を保つため)。リスト17-16で示されるように、settings.ymlファイルの中でこれらを有効にしなければなりません。


[116] Edit ↑TOP

リスト17-16 - frontend/config/settings.ymlの中で、プラグインモジュールを有効にする


[117] Edit ↑TOP
all:
  .settings:
    enabled_modules:  [default, sfMyPluginModule]

[118] Edit ↑TOP

これはプラグインモジュールを必要としないアプリケーションが誤ってそのプラグインを利用できるように設定する状況を避けるためです。その状況ではセキュリティの欠陥を公開してしまう可能性があります。frontendモジュールとbackendモジュールを提供するプラグインを考えて下さい。frontendモジュールはfrontendアプリケーション専用として、backendモジュールはbackendアプリケーション専用として有効にする必要があります。プラグインモジュールがデフォルトで有効にされない理由はそういうわけです。


[119] Edit ↑TOP
The default module is the only enabled module by default. That's not really a plug-in module, because it resides in the framework, in sfConfig::get('sf_symfony_lib_dir')/controller/default/. This is the module that provides the congratulations pages, and the default error pages for 404 and credentials required errors. If you don't want to use the symfony default pages, just remove this module from the enabled_modules setting.


[120] Edit ↑TOP

インストールしたプラグインの一覧を表示する


[121] Edit ↑TOP

プロジェクトのplugins/ディレクトリをざっと見るとプラグインがインストールされている場所がわかります。そしてplugin:listタスクはより詳細な情報を示します: バージョン番号とインストールしたそれぞれのプラグインのチャネル名です。


[122] Edit ↑TOP

リスト17-17 - インストールされたプラグインの一覧


[123] Edit ↑TOP
> cd myproject
> php symfony plugin:list

Installed plugins:
sfPrototypePlugin               1.0.0-stable # plugins.symfony-project.com (symfony)
sfSuperCachePlugin              1.0.0-stable # plugins.symfony-project.com (symfony)
sfThumbnail                     1.1.0-stable # plugins.symfony-project.com (symfony)

[124] Edit ↑TOP

プラグインのアップグレードとアンインストール


[125] Edit ↑TOP

PEARのプラグインをアンインストールするには、リスト17-18で示されるように、プロジェクトのrootディレクトリからplugin:uninstallタスクを呼び出します。プラグインの名前にプラグインをインストールしたチャネル名を接頭辞として追加しなければなりません(このチャネルを決めるためにplugin:listタスクを使います)。


[126] Edit ↑TOP

リスト17-18 - プラグインをアンインストールする


[127] Edit ↑TOP
> cd myproject
> php symfony plugin:uninstall sfPrototypePlugin
> php symfony cc

[128] Edit ↑TOP

アーカイブからインストールしたプラグインもしくはSVNリポジトリからインストールしたプラグインをアンインストールするためには、プロジェクトのplugins/web/ディレクトリからプラグインのファイルを手動で削除してキャッシュをクリアします。


[129] Edit ↑TOP

プラグインをアップグレードするには、plugin:upgradeタスク(PEARプラグインの場合)もしくはsvn updateを実行します(バージョン管理システムのリポジトリからプラグインを入手した場合)。アーカイブからインストールしたプラグインは簡単にアップグレードできません。


[130] Edit ↑TOP

プラグインの分析


[131] Edit ↑TOP

プラグインはPHPで書かれています。アプリケーションの編成方法を理解しているのであれば、プラグインの構造を理解できます。


[132] Edit ↑TOP

プラグインのファイル構造


[133] Edit ↑TOP

プラグインのディレクトリはおおよそプロジェクトのディレクトリと同じように編成されています。必要な時にsymfonyによって自動的にロードされるようにするためにプラグインファイルは正しいディレクトリに存在しなければなりません。ファイル構造の記述に関してはリスト17-19をご覧下さい。


[134] Edit ↑TOP

リスト17-19 - プラグインのファイル構造


[135] Edit ↑TOP
pluginName/
  config/
    *schema.yml        // データスキーマ
    *schema.xml
    config.php         // 特定のプラグイン設定
  data/
    generator/
      sfPropelAdmin
        */             // administrationジェネレータテーマ
          template/
          skeleton/
    fixtures/
      *.yml            // フィクスチャファイル
  lib/
    *.php              // クラス
    helper/
      *.php            // ヘルプ
    model/
      *.php            // モデルクラス
    task/
      *Task.class.php  // CLIタスク
  modules/
    */                 // モジュール
      actions/
        actions.class.php
      config/
        module.yml
        view.yml
        security.yml
      templates/
        *.php
      validate/
        *.yml
  web/
    *                  // アセット

[136] Edit ↑TOP

プラグインの機能


[137] Edit ↑TOP

プラグインは多くのものを含みます。コマンドラインでタスクを呼び出すときに実行中のアプリケーションはこれらの内容を自動的に考慮します。しかしプラグインを適切に機能させるには、いくつかの規約を遵守しなければなりません:


[138] Edit ↑TOP
  • データベースのスキーマはpropel-タスクによって検出されます。propel-build-modelタスクを呼び出すと、プロジェクトモデルとすべてのプラグインモデルがリビルドされます。リスト17-20で示されるように、 プラグインスキーマは常にplugins.pluginName.lib.model形式でpackage属性を持つことに注意して下さい。

[139] Edit ↑TOP

リスト17-20 - myPlugin/config/schema.ymlの中のスキーマ宣言の例


[140] Edit ↑TOP
propel:
  _attributes:    { package: plugins.myPlugin.lib.model }
  my_plugin_foobar:
    _attributes:    { phpName: myPluginFoobar }
      id:
      name:           { type: varchar, size: 255, index: unique }
      ...

[141] Edit ↑TOP
  • プラグインの設定はプラグインのブートストラップスクリプト(config.php)に含まれています。このファイルはアプリケーションとプロジェクト設定の後で実行されるので、symfonyはその時点で既に起動しています。例えば、既存のクラスをイベントリスナもしくはビヘイビアで拡張するためです。
  • プラグインのdata/fixtures/ディレクトリに設置されたフィクスチャファイルはpropel:load-dataタスクで処理されます。
  • プロジェクトのlib/フォルダに設置されたクラスのようにカスタムクラスはオートロードされます。
  • テンプレートの中でuse_helper()ヘルパを呼び出すときにヘルパは自動的に発見されます。これらはプラグインのlib/ディレクトリの1つのhelper/サブディレクトリに存在しなければなりません。
  • myplugin/lib/model/ディレクトリ内のモデルクラスは(myplugin/lib/model/om/ディレクトリとmyplugin/lib/model/map/ディレクトリ)内部のPropelビルダによって生成されたモデルクラスを専門に扱います。もちろんこれらもオートロードされます。独自プロジェクトのディレクトリ内で生成されたプラグインのモデルクラスはオーバーライドできません。
  • プラグインをインストールすればタスクはsymfonyコマンドですぐに利用できます。プラグインは新しいタスクを追加もしくは既存のものを上書きできます。タスク用にプラグインの名前を名前空間として使うことは最良の習慣です。プラグインに追加されたものを含めて、利用可能なタスクの一覧を見るためには、php symfonyを入力して下さい。
  • アプリケーションのenabled_modules設定で宣言すれば、モジュールは外部からアクセス可能な新しいアクションを提供します。
  • サーバはウェブアセット(イメージ、スクリプト、スタイルシートなど)を利用できます。コマンドライン経由でプラグインをインストールしたとき、システムが許可するのであればsymfonyはプロジェクトのweb/ディレクトリにシンボリックリンクを作るもしくはweb/ディレクトリの内容をプロジェクトのディレクトリにコピーします。プラグインがアーカイブもしくはバージョン管理ツールのリポジトリからインストールされた場合、手動でweb/ディレクトリにコピーしなければなりません(プラグインに添付されているREADMEに記載されています)。

[142] Edit ↑TOP
: Registering routing rules in a Plug-in A plug-in can add new rules to the routing system, but it is not recomandable to do it by using a custom routing.yml configuration file. This is because the order in which rules are defined is very important, and the simple cascade configuration system of YAML files in symfony would mess this order up. Instead, plug-ins need to register an event listener on the routing.load_configuration event and manually prepend rules in the listener:


// in plugins/myPlugin/config/config.php
$this->dispatcher->connect('routing.load_configuration', array('myPluginRouting', 'listenToRoutingLoadConfigurationEvent'));

// in plugins/myPlugin/lib/myPluginRouting.php
class myPluginRouting
{
  static public function listenToRoutingLoadConfigurationEvent(sfEvent $event)
  {
    $routing = $event->getSubject();
    // add plug-in routing rules on top of the existing ones
    $routing->prependRoute('my_route', new sfRoute('/my_plugin/:action', array('module' => 'myPluginAdministrationInterface')));
  }
}

[143] Edit ↑TOP

手動によるプラグインのセットアップ


[144] Edit ↑TOP

plugin:installタスクが独自に処理できない要素がいくつかあります。インストール作業の合間にこれらを手動でセットアップする必要があります:


[145] Edit ↑TOP
  • カスタムのアプリケーション設定はプラグインのコードで使われますが(例えば、sfConfig::get('app_myplugin_foo')を利用する)、デフォルトの値をプラグインのconfig/ディレクトリに設置されたapp.ymlファイルに設定できません。デフォルトの値を処理するには、sfConfig::get()メソッドの2番目の引数を使います。設定はまだアプリケーションレベルでオーバーライドできます(リスト17-26で例をご覧下さい)。
  • カスタムのルーティングルールはアプリケーションのrouting.ymlに手動で追加しなければなりません。
  • カスタムのフィルタはアプリケーションのfilters.ymlに手動で追加しなければなりません。
  • カスタムのファクトリはアプリケーションのfactories.ymlに手動で追加しなければなりません。

[146] Edit ↑TOP

一般的に言えば、アプリケーションの設定ファイルの1つに帰結するようなすべての設定は手動で追加しなければなりません。このような手動のセットアップが必要なプラグインはREADMEファイルで詳細なインストール方法を説明しています。


[147] Edit ↑TOP

アプリケーションのためにプラグインをカスタマイズする


[148] Edit ↑TOP

プラグインをカスタマイズしたいときは、決してplugins/ディレクトリ内で見つかるコードを変更してはなりません。これを行うと、プラグインをアップグレードするときにすべての修正内容が失われてしまいます。必要なカスタマイズを行うために、プラグインはカスタムの設定を提供し、オーバーライドをサポートします。


[149] Edit ↑TOP

リスト17-21で示されるように、よく設計されたプラグインはアプリケーションのapp.ymlファイルで変更できる設定を利用します。


[150] Edit ↑TOP

リスト17-21 - アプリケーションの設定を利用するプラグインをカスタマイズする


[151] Edit ↑TOP

// プラグインのコードの例
$foo = sfConfig::get('app_my_plugin_foo', 'bar');

// アプリケーションのapp.ymlで'foo'のデフォルト値('bar')を変更する
all:
  my_plugin:
    foo:       barbar

[152] Edit ↑TOP

モジュールの設定とデフォルトの値はプラグインのREADMEファイルで詳しく説明されています。


[153] Edit ↑TOP

独自のアプリケーション内部で同じ名前のモジュールを作成することでプラグインモジュールのデフォルトの内容を置き換えることができます。プラグイン要素の代わりにアプリケーション要素が使われているので、本当の上書きではありません。プラグインの名前と同じ名前のテンプレートと設定ファイルを作ればプラグインモジュールは立派に機能します。


[154] Edit ↑TOP

一方で、アクションをオーバーライドする機能を持つモジュールをプラグインに持たせたい場合、プラグインモジュール内のactions.class.phpのメソッドがアプリケーションモジュールのactions.class.phpによって継承できるように、actions.class.phpは空でなければならずオートロードクラスから継承しなければなりません。お手本に関してはリスト17-22を参照して下さい。


[155] Edit ↑TOP

リスト17-22 - プラグインのアクションをカスタマイズする


[156] Edit ↑TOP

// myPlugin/modules/mymodule/lib/myPluginmymoduleActions.class.phpの中
class myPluginmymoduleActions extends sfActions
{
  public function executeIndex()
  {
    // ここに何らかのコード
  }
}

// myPlugin/modules/mymodule/actions/actions.class.phpにて

require_once dirname(__FILE__).'/../lib/myPluginmymoduleActions.class.php';

class mymoduleActions extends myPluginmymoduleActions
{
  // 無し
}

// frontend/modules/mymodule/actions/actions.class.phpにて
class mymoduleActions extends myPluginmymoduleActions
{
  public function executeIndex()
  {
    // ここでプラグインのコードをオーバーライドする
  }
}

[157] Edit ↑TOP

[158] Edit ↑TOP

プラグインの書き方


[159] Edit ↑TOP

plugin:installタスクではPEARパッケージ形式のプラグインのみがインストールされます。このようなプラグインは公式サイトの専用ページ、PEARチャネル経由もしくはダウンロードできる通常のファイルとして配布されていることを覚えておいて下さい。プラグインを編集したい場合は、単純なアーカイブよりもPEARパッケージとして公開した方がベターでしょう。加えて、プラグインをPEARパッケージにすればアップグレード作業が簡単になり、依存関係の宣言が可能で、自動的にアセットをweb/ディレクトリにデプロイできます。


[160] Edit ↑TOP

ファイルの構成


[161] Edit ↑TOP

新しい機能を開発し、プラグインとしてパッケージにすることを考えてみましょう。最初の段階はファイルを論理的に編成して、symfonyのロードメカニズムが必要なときにこれらのファイルを見つけることができるようにしましょう。この目的のために、リスト17-19で示されているディレクトリ構造に従う必要があります。リスト17-23はsfSamplePluginプラグインのためのファイル構造の例を示しています。


[162] Edit ↑TOP

リスト17-23 - プラグインとしてパッケージにするファイルの一覧の例


[163] Edit ↑TOP
sfSamplePlugin/
  README
  LICENSE
  config/
    schema.yml
  data/
    fixtures/
      fixtures.yml
  lib/
    model/
      sfSampleFooBar.php
      sfSampleFooBarPeer.php
    task/
      sfSampleTask.class.php
    validator/
      sfSampleValidator.class.php
  modules/
    sfSampleModule/
      actions/
        actions.class.php
      config/
        security.yml
      lib/
        BasesfSampleModuleActions.class.php
      templates/
        indexSuccess.php
  web/
    css/
      sfSampleStyle.css
    images/
      sfSampleImage.png

[164] Edit ↑TOP

編集に関して、プラグインのディレクトリの位置(リスト17-23のsfSamplePlugin/)は重要ではありません。これはディスク上の任意の場所に設置できます。


[165] Edit ↑TOP
既存のプラグインを練習問題として考え、初めてプラグインを作る際には、これらの名前の規約とファイルの構造を再現してみて下さい。


[166] Edit ↑TOP

package.xmlファイルを作る


[167] Edit ↑TOP

プラグイン編集の次の段階はプラグインディレクトリのrootでpackage.xmlファイルを追加することです。package.xmlはPEARの構文に従います。リスト17-24の典型的なsymfonyプラグインのpackage.xmlをご覧下さい。


[168] Edit ↑TOP

リスト17-24 - symfonyプラグイン用のpackage.xml


[169] Edit ↑TOP

<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.4.6" 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>sfSamplePlugin</name>
 <channel>plugins.symfony-project.org</channel>
 <summary>symfony sample plugin</summary>
 <description>Just a sample plugin to illustrate PEAR packaging</description>
 <lead>
  <name>Fabien POTENCIER</name>
  <user>fabpot</user>
  <email>fabien.potencier@symfony-project.com</email>
  <active>yes</active>
 </lead>
 <date>2006-01-18</date>
 <time>15:54:35</time>
 <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.org/license">MIT license</license>
 <notes>-</notes>
 <contents>
  <dir name="/">
   <file role="data" name="README" />
   <file role="data" name="LICENSE" />
   <dir name="config">
    <!-- model -->
    <file role="data" name="schema.yml" />
   </dir>
   <dir name="data">
    <dir name="fixtures">
     <!-- fixtures -->
     <file role="data" name="fixtures.yml" />
    </dir>
   </dir>
   <dir name="lib">
    <dir name="model">
     <!-- model classes -->
     <file role="data" name="sfSampleFooBar.php" />
     <file role="data" name="sfSampleFooBarPeer.php" />
    </dir>
    <dir name="task">
     <!-- tasks -->
     <file role="data" name="sfSampleTask.class.php" />
    </dir>
    <dir name="validator">
     <!-- validators -->
     <file role="data" name="sfSampleValidator.class.php" />
    </dir>
   </dir>
   <dir name="modules">
    <dir name="sfSampleModule">
     <file role="data" name="actions/actions.class.php" />
     <file role="data" name="config/security.yml" />
     <file role="data" name="lib/BasesfSampleModuleActions.class.php" />
     <file role="data" name="templates/indexSuccess.php" />
    </dir>
   </dir>
   <dir name="web">
    <dir name="css">
     <!-- stylesheets -->
     <file role="data" name="sfSampleStyle.css" />
    </dir>
    <dir name="images">
     <!-- images -->
     <file role="data" name="sfSampleImage.png" />
    </dir>
   </dir>
  </dir>
 </contents>
 <dependencies>
  <required>
   <php>
    <min>5.1.0</min>
   </php>
   <pearinstaller>
    <min>1.4.1</min>
   </pearinstaller>
   <package>
    <name>symfony</name>
    <channel>pear.symfony-project.com</channel>
    <min>1.1.0</min>
    <max>1.3.0</max>
    <exclude>1.3.0</exclude>
   </package>
  </required>
 </dependencies>
 <phprelease />
 <changelog />
</package>

[170] Edit ↑TOP

ここで注目すべき部分は<contents>タグと<dependencies>タグで、次に説明します。残りのタグに関しては、symfony固有のものではありませんので、package.xmlフォーマットに関する詳細な内容はPEARオンラインマニュアル(http://pear.php.net/manual/ja/) を参照して下さい。


[171] Edit ↑TOP

内容


[172] Edit ↑TOP

<contents>タグはプラグインのファイル構造を記述しなければならない場所です。このタグはコピーするファイルとその場所をPEARに伝えます。<dir>タグと<file>タグでファイル構造を記述して下さい。すべての<file>タグはrole="data"属性を持たなければなりません。リスト17-24の<contents>タグの部分はリスト17-23の正しいディレクトリ構造を記載しています。


[173] Edit ↑TOP
<dir>タグの使用は義務ではありません。<file>タグ内で相対パスをnameの値として利用できるからです。package.xmlファイルを読みやすくするためにお勧めです。


[174] Edit ↑TOP

プラグインの依存関係


[175] Edit ↑TOP

任意のバージョンのPHP、PEAR、symfony、PEARパッケージ、もしくは他のプラグインの一式で動くようにプラグインは設計されています。<dependencies>タグでこれらの依存関係を宣言すれば必要なパッケージが既にインストールされていることを確認してそうでなければ例外を起動するようPEARに伝えることになります。


[176] Edit ↑TOP

最小要件として、少なくとも開発環境に対応したPHP、PEARとsymfonyへの依存関係を常に宣言します。何を追加すればよいのかわからなければ、PHP 5.1、PEAR 1.4とsymfony 1.0の要件を追加して下さい。


[177] Edit ↑TOP

それぞれのプラグインに対してsymfonyの最大のバージョン番号を追加することも推奨されます。これによって上位バージョンのsymfonyでプラグインを使うときにエラーメッセージが表示され、プラグインを再リリースする前にこのバージョンでプラグインが正しく動作するのかを確認することをプラグインの作者に義務づけます。無言でプラグインの動作が失敗するよりも警告を発してダウンロードとアップグレードする方がベターです。


[178] Edit ↑TOP

プラグインを依存関係のあるものとして指定すれば、ユーザはプラグインとすべての依存関係を1つのコマンドでインストールできるようになります:


[179] Edit ↑TOP
> php symfony plugin:install --install-deps sfSamplePlugin

[180] Edit ↑TOP

プラグインをビルドする


[181] Edit ↑TOP

PEARコンポーネントはパッケージの.tgzアーカイブを作るコマンド(pear package)を持ちます。リスト17-25では、package.xmlを含むディレクトリでこのコマンドを呼び出しています。


[182] Edit ↑TOP

リスト17-25 - プラグインをPEARパッケージにする


[183] Edit ↑TOP
> cd sfSamplePlugin
> pear package

Package sfSamplePlugin-1.0.0.tgz done

[184] Edit ↑TOP

一旦プラグインのパッケージがビルドされたら、リスト17-26で示されるように、あなたの環境にこれをインストールして動作を確認して下さい。


[185] Edit ↑TOP

リスト17-26 - プラグインをインストールする


[186] Edit ↑TOP
> cp sfSamplePlugin-1.0.0.tgz /home/production/myproject/
> cd /home/production/myproject/
> php symfony plugin:install sfSamplePlugin-1.0.0.tgz

[187] Edit ↑TOP

<contents>タグにある説明に従って、パッケージにされたファイルは最終的にプロジェクトの異なるディレクトリに設置されます。リスト17-27はインストールの後でsfSamplePluginのファイルが設置される場所を示しています。


[188] Edit ↑TOP

リスト17-27 - プラグインファイルはplugin/ディレクトリとweb/ディレクトリにインストールされる


[189] Edit ↑TOP
plugins/
  sfSamplePlugin/
    README
    LICENSE
    config/
      schema.yml
    data/
      fixtures/
        fixtures.yml
    lib/
      model/
        sfSampleFooBar.php
        sfSampleFooBarPeer.php
      task/
        sfSampleTask.class.php
      validator/
        sfSampleValidator.class.php
    modules/
      sfSampleModule/
        actions/
          actions.class.php
        config/
          security.yml
        lib/
          BasesfSampleModuleActions.class.php
        templates/
          indexSuccess.php
web/
  sfSamplePlugin/               ## システム次第で、コピーもしくはシンボリックリンク
    css/
      sfSampleStyle.css
    images/
      sfSampleImage.png

[190] Edit ↑TOP

このプラグインの挙動をアプリケーションでテストして下さい。きちんと動くのであれば、プラグインを複数のプロジェクトをまたがって配布するもしくはsymfonyコミュニティに寄付する準備ができています。


[191] Edit ↑TOP

公式サイトでプラグインを配布する


[192] Edit ↑TOP

symfonyのプラグインは以下の手順に従ってsymfony-project.orgのウェブサイトで配布されるときに最も幅広い利用者を得ます。独自プラグインを次のような方法で配布できます(訳注:最新の配布方法はこちらを参照):


[193] Edit ↑TOP
  1. READMEファイルにプラグインのインストール方法と使い方が、LICENSEファイルにはライセンスの詳細が記述されていることを確認する。READMEはMarkdownの構文 (http://daringfireball.net/projects/markdown/syntax) で記述する。
  2. 公式サイトのアカウント (http://www.symfony-project.org/user/new) を作りプラグインのページ (http://www.symfony-project.org/plugins/new) を作る。
  3. pear packageコマンドを呼び出してプラグイン用のPEARパッケージを作りテストする。PEARパッケージの名前はsfSamplePlugin-1.0.0.tgz (1.0.0はプラグインのバージョン)でなければならない。
  4. PEARパッケージをアップロードする (sfSamplePlugin-1.0.0.tgz)。
  5. アップロードしたプラグインは一覧ページ (http://www.symfony-project.org/plugins/) に表示される。

[194] Edit ↑TOP

この手続きを行えば、ユーザはプロジェクトのディレクトリで次のコマンドを入力するだけでプラグインをインストールできるようになります:


[195] Edit ↑TOP
> php symfony plugin:install sfSamplePlugin

[196] Edit ↑TOP

命名規約


[197] Edit ↑TOP

plugin/ディレクトリをきれいに保つために、すべてのプラグインの名前がcamelCaseでありPluginの接尾辞で終わることを確認して下さい(例えば、shoppingCartPluginfeedPlugin)。プラグインに名前を付ける前に、同じ名前のプラグインが存在しないことを確認して下さい。


[198] Edit ↑TOP
Propelに依存するプラグインの名前はPropelを含みます。例えば、Propelのデータアクセスオブジェクトを利用する認証プラグインはsfPropelAuthという名前になります。


[199] Edit ↑TOP

プラグインには使用条件と選んだライセンスを説明するLICENSEファイルを常に含めなければなりません。バージョンの履歴、プラグインの目的、効果、インストールと設定の手引きなどを含めることも推奨されます。


[200] Edit ↑TOP

要約


[201] Edit ↑TOP

symfonyのクラスはアプリケーションレベルで修正できる機能を提供するsfMixerフックを含みます。ミックスイン(mixin)のメカニズムはPHPの制約が禁止している実行時のクラスの多重継承とオーバーライドを可能にします。ですのでそのためにコアクラスを修正しなければならないとしても、またファクトリ(factory)の設定がそこに存在するとしてもsymfonyの機能を簡単に拡張できます。


[202] Edit ↑TOP

既に多くの拡張機能(エクステンション)が存在し、プラグインとしてパッケージが作成されています。symfonyのコマンドラインによってインストール、アップグレード、アンインストールするのが簡単です。プラグインをPEARパッケージを作成するのと同じぐらい簡単で、複数のアプリケーションをまたがって再利用できます。


[203] Edit ↑TOP

symfony公式サイトのwikiには多くのプラグインが含まれ、あなた自身のプラグインも追加できます。これであなたは方法を理解したので、私達symfonyの開発者はあなたが多くの便利な拡張機能でsymfonyコアを強化して下さることを望んでおります!


Comments

下翻訳からコピー完了 (2009-02-11 10:38:14 brtRiver)

Menu

Documentation



Latest Histories

Listing 17-9 - Extending ...
brtRiver(2009-02-11 10:48:19)
- prod: log ...
brtRiver(2009-02-11 10:47:21)
The symfony wiki contains ...
brtRiver(2009-02-11 10:37:41)
Many such extensions alre ...
brtRiver(2009-02-11 10:37:23)
The symfony classes conta ...
brtRiver(2009-02-11 10:36:43)
Summary ------- ...
brtRiver(2009-02-11 10:36:26)
Plug-ins should always in ...
brtRiver(2009-02-11 10:36:12)
>**NOTE** >Plug-ins ...
brtRiver(2009-02-11 10:35:56)
To keep the `plugins/` di ...
brtRiver(2009-02-11 10:35:40)
#### Naming Conventions ...
brtRiver(2009-02-11 10:35:23)
> php symfony plug ...
brtRiver(2009-02-11 10:35:08)
If you follow this proced ...
brtRiver(2009-02-11 10:34:54)
1. Make sure the `READM ...
brtRiver(2009-02-11 10:34:40)
A symfony plug-in gets th ...
brtRiver(2009-02-11 10:34:17)
#### Hosting Your Plug-In ...
brtRiver(2009-02-11 10:33:53)
Test the way the plug-in ...
brtRiver(2009-02-11 10:33:30)
plugins/ sfSamp ...
brtRiver(2009-02-11 10:33:13)
Listing 17-27 - The Plug- ...
brtRiver(2009-02-11 10:32:51)
According to their descri ...
brtRiver(2009-02-11 10:32:31)
> cp sfSamplePlugi ...
brtRiver(2009-02-11 10:32:17)

Untranslated