From 22ff5c4b76615569c65002361032be55adffae24 Mon Sep 17 00:00:00 2001 From: 77web Date: Thu, 23 Oct 2014 21:12:09 +0900 Subject: [PATCH 1/7] [best practice] add index.rst --- best_practices/index.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 best_practices/index.rst diff --git a/best_practices/index.rst b/best_practices/index.rst new file mode 100644 index 0000000..492293f --- /dev/null +++ b/best_practices/index.rst @@ -0,0 +1,17 @@ +Symfony 公式ベストプラクティス +=============================== + +.. toctree:: + :hidden: + + introduction + creating-the-project + configuration + business-logic + controllers + templates + forms + i18n + security + web-assets + tests From 13c3177eb54fcad1340c368b776bd71f69d12dff Mon Sep 17 00:00:00 2001 From: 77web Date: Thu, 23 Oct 2014 21:12:24 +0900 Subject: [PATCH 2/7] [best practice] add forms.rst --- best_practices/forms.rst | 206 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 best_practices/forms.rst diff --git a/best_practices/forms.rst b/best_practices/forms.rst new file mode 100644 index 0000000..59d2fb7 --- /dev/null +++ b/best_practices/forms.rst @@ -0,0 +1,206 @@ +フォーム +========== + +フォームは、取り扱う範囲が広く、膨大な機能があるために、誤って使用されることが最も多いコンポーネントです。 +この章では、フォームを活用して素早く実装できるようにすることができるベストプラクティスを説明します。 + +フォームを構築する +------------------- + +.. best-practice:: + + フォームをPHPクラスで定義しましょう。 + +Formコンポーネントでは、コントローラーのコードの中でもフォームを構築できるようになっています。はっきり言って、フォームをどこか他の場所で使う予定がないのなら、それでも全く構いません。 +しかし、コードの整理と再利用のために、あらゆるフォームをPHPクラスとして定義することをおすすめします。 + +.. code-block:: php + + namespace AppBundle\Form; + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + class PostType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('title') + ->add('summary', 'textarea') + ->add('content', 'textarea') + ->add('authorEmail', 'email') + ->add('publishedAt', 'datetime') + ; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'AppBundle\Entity\Post' + )); + } + + public function getName() + { + return 'post'; + } + } + +このフォームクラスを利用するためには、 ``createForm`` を使い、この新しいクラスのインスタンスを渡してください。 + +.. code-block:: php + + use AppBundle\Form\PostType; + // ... + + public function newAction(Request $request) + { + $post = new Post(); + $form = $this->createForm(new PostType(), $post); + + // ... + } + +フォームをサービスとして登録する +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`フォームタイプをサービスとして定義する`_ こともできます。 +しかし、多くの場所でフォームを再利用する予定があったり、他のフォームに直接あるいは `collection`_ として埋め込む予定がない限り、この方法は推奨 *しません* 。 + +ほとんどのフォームは、何かを編集したり作成したりするのに使われるだけなので、フォームをサービスとして登録するのはやりすぎです。コントローラーの中で使われているフォームクラスがどれなのかわかりにくくなってしまいます。 + +フォームのボタン定義 +------------------------- + +フォームクラスは、フォームがアプリケーション内の *どこで* 使われるのかなるべく無関心であるべきです。後から再利用しやすくなります。 + +.. best-practice:: + + ボタンはテンプレート上で追加し、フォームクラスやコントローラーに書かないようにしましょう。 + +Symfony 2.5以降では、フォームにボタンの定義を追加することができます。フォームをレンダリングするテンプレートを簡略にするのに良い方法ですが、フォームクラスの中に直接ボタンを定義してしまうと、フォームの扱う範囲を極端に狭めてしまうことになるのです。 + +.. code-block:: php + + class PostType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + // ... + ->add('save', 'submit', array('label' => 'Create Post')) + ; + } + + // ... + } + +このフォームは投稿を新規作成する用途で作られたの *かも* しれませんが、投稿を編集する場面で再利用しようとすると、ボタンのラベルが間違っていることになります。 +その代わりに、コントローラーでフォームボタンを定義する開発者もいるでしょう。 + +.. code-block:: php + + namespace AppBundle\Controller\Admin; + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use AppBundle\Entity\Post; + use AppBundle\Form\PostType; + + class PostController extends Controller + { + // ... + + public function newAction(Request $request) + { + $post = new Post(); + $form = $this->createForm(new PostType(), $post); + $form->add('submit', 'submit', array( + 'label' => 'Create', + 'attr' => array('class' => 'btn btn-default pull-right') + )); + + // ... + } + } + + +これも重大な誤りです。というのも、プレゼンテーション用のマークアップ(ラベル、CSSクラスなど)を純粋なPHPコードの中に混在させてしまっているからです。 +関心の分離は常に意識すべきプラクティスであり、全ての見た目に関係する物事はviewレイヤーに置くべきでしょう。 + +.. code-block:: html+jinja + +
+ {{ form_widget(form) }} + + +
+ +フォームをレンダリングする +--------------------------- + +フォームをレンダリングする方法は、フォーム全体を一行でレンダリングするのから、フィールドの各パーツを個別にレンダリングするのまで、多岐に渡ります。 +一番良い方法は、アプリケーションでどこまでのカスタマイズが必要かによって異なります。 + +一番シンプルな方法(特に開発中に便利な方法)はHTMLのフォームタグを書いてから、 ``form_widget()`` を使って全てのフィールドを一度にレンダリングする方法です。 + +.. code-block:: html+jinja + +
+ {{ form_widget(form) }} +
+ +.. best-practice:: + + フォームの開始タグや終了タグに ``form()`` や ``form_start()`` を使わないようにしましょう。 + +Symfony に慣れた開発者なら、 ``
`` タグを ``form_start()`` や ``form()`` といったヘルパーを使わずにレンダリングしていることに気づくでしょう。 +ヘルパーは便利ですが、反面、僅かな利便性と引き換えにわかりやすさを損なっているのです。 + +.. tip:: + + 削除フォームは例外です。削除フォームはただ一つだけのボタンなので、ショートカットによる利便性を選択しても良いでしょう。 + +もしフィールドのレンダリング内容についてもっとコントロールしたければ、 ``form_widget(form)`` を削除してフィールドを個別にレンダリングしたほうが良いでしょう。 +フィールドを個別にレンダリングする方法の詳細と、フォームテーマを使ってフォームレンダリングをアプリケーション全体でカスタマイズする方法については `How to Customize Form Rendering`_ を参考にしてください。 + +フォーム送信を扱う +--------------------- + +フォーム送信を受け取る処理は、大抵の場合、一種の定型文になります。 + +.. code-block:: php + + public function newAction(Request $request) + { + // フォームを構築 ... + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($post); + $em->flush(); + + return $this->redirect($this->generateUrl( + 'admin_post_show', + array('id' => $post->getId()) + )); + } + + // テンプレートをレンダリング + } + +大事なことは2つだけです。まず、フォームのレンダリングとフォーム送信の受付に一つのアクションを使いましょう。 +例えば、フォームのレンダリング *だけ* を行う ``newAction`` とフォーム送信の受付だけを行う ``createAction`` を両方作ったとします。この2つのアクションはほとんど同じ内容になるでしょう。ならば、 ``newAction`` に全てを処理させるほうがもっとシンプルになります。 + +次に、わかりやすくするために ``if`` 条件文に ``$form->isSubmitted()`` を使いましょう。 +``isValid()`` は内部的に最初に ``isSubmitted()`` を呼び出しているので、技術的には必要のないことです。しかし、これがなければ、一見してフォーム送信を受け付ける処理が *常に* 行われるように(GETリクエストの時であっても)見えてしまい、処理の流れが読み取りにくくなってしまうのです。 + +.. _`フォームをサービスとして登録する`: http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-your-field-type-as-a-service +.. _`collection`: http://symfony.com/doc/current/reference/forms/types/collection.html +.. _`How to Customize Form Rendering`: http://symfony.com/doc/current/cookbook/form/form_customization.html +.. _`form event system`: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html From d24ac0983fa693e44a5d0b888000b1c19844d3ea Mon Sep 17 00:00:00 2001 From: 77web Date: Thu, 23 Oct 2014 21:15:32 +0900 Subject: [PATCH 3/7] [best practice] add templates.rst --- best_practices/templates.rst | 148 +++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 best_practices/templates.rst diff --git a/best_practices/templates.rst b/best_practices/templates.rst new file mode 100644 index 0000000..f931f4b --- /dev/null +++ b/best_practices/templates.rst @@ -0,0 +1,148 @@ +テンプレート +============== + +20年前にPHPが作られた頃、開発者達はPHPのシンプルさや、PHPコードのHTMLとの親和性を愛していました。 +しかし、時間が経つとともに、テンプレートをもっとうまく扱うために他のテンプレート言語(例えば `Twig`_ )が作られるようになりました。 + +.. best-practice:: + + テンプレートには Twig を使いましょう。 + +一般的に、PHPのテンプレートはTwigよりも冗長になります。というのも、PHPのテンプレートは継承、自動エスケープ、フィルタや関数への名前付き引数などの、最新の機能を備えていないからです。 +TwigはSymfonyでのデフォルトのテンプレート言語で、PHP以外も含めた全てのテンプレートエンジンの中で最大のコミュニティサポートがあり、Drupal 8 のような負荷の高いプロジェクトでも使われています。 + +それに加えて、TwigはSymfony3.0で動作が保証される唯一のテンプレートエンジンです。 +実のところ、PHPテンプレートエンジンは、Symfonyの公式なサポートから除外されるでしょう。 + +テンプレートファイルの配置 +---------------------------- + +.. best-practice:: + + アプリケーションのテンプレートファイルは、全て ``app/Resources/views`` ディレクトリに置きましょう。 + +伝統的に、Symfonyの開発者達はアプリケーションのテンプレートファイルを、それぞれのバンドルの ``Resources/views`` ディレクトリに保存してきました。 +そして、その場所を参照する仮想的な名前を使っていました(例えば ``AcmeDemoBundle:Default:index.html.twig`` ) + +しかし、アプリケーションで使用するテンプレートについては、 ``app/Resources/views/`` ディレクトリに保存するほうが便利なのです。 +初心者にとっては、この保存先を使うことによって、ビューの名前を劇的にシンプルにすることができます。 + +================================================== ================================== +バンドルに保存されたテンプレート ``app/`` に保存されたテンプレート +================================================== ================================== +``AcmeDemoBunde:Default:index.html.twig`` ``default/index.html.twig`` +``::layout.html.twig`` ``layout.html.twig`` +``AcmeDemoBundle::index.html.twig`` ``index.html.twig`` +``AcmeDemoBundle:Default:subdir/index.html.twig`` ``default/subdir/index.html.twig`` +``AcmeDemoBundle:Default/subdir:index.html.twig`` ``default/subdir/index.html.twig`` +================================================== ================================== + +もう一つのメリットは、テンプレートを一カ所に集中させることができ、デザイナーの仕事が簡単になることです。多数のバンドル内のディレクトリに分かれたテンプレートを探しまわる必要がなくなるのです。 + +Twigエクステンション +--------------------- + +.. best-practice:: + + Twigエクステンションは ``AppBundle/Twig/`` ディレクトリに置き、 ``app/config/services.yml`` を使って設定しましょう。 + +開発中のアプリケーションに、独自の ``md2html`` というTwigフィルタが必要だと想像してください。 +Markdown形式のコンテンツをHTMLに変換するフィルタです。 + +このフィルタを仕える王にするには、まず、Markdownパーサーの `Parsedown`_ をプロジェクトの新しい依存ライブラリとしてインストールします。 + +.. code-block:: bash + + $ composer require erusev/parsedown + +次に、 ``Markdown`` サービスを新しく作成します。後でTwigエクステンションから利用するためのサービスです。 +サービスの定義はクラスのパスだけでできます。 + +.. code-block:: yaml + + # app/config/services.yml + services: + # ... + markdown: + class: AppBundle\Utils\Markdown + +そして、 ``Markdown`` クラスには、MarkdownをHTMLに変換するメソッドが一つだけ必要です。:: + + namespace AppBundle\Utils; + + class Markdown + { + private $parser; + + public function __construct() + { + $this->parser = new \Parsedown(); + } + + public function toHtml($text) + { + $html = $this->parser->text($text); + + return $html; + } + } + +次に、新しいTwigエクステンションを作成し、``Twig_SimpleFilter`` を使って ``md2html`` という新しいフィルタを作りましょう。 +Twigエクステンションには、新しく定義したばかりの ``markdown`` サービスをコンストラクタで注入します。 + +.. code-block:: php + + namespace AppBundle\Twig; + + use AppBundle\Utils\Markdown; + + class AppExtension extends \Twig_Extension + { + private $parser; + + public function __construct(Markdown $parser) + { + $this->parser = $parser; + } + + public function getFilters() + { + return array( + new \Twig_SimpleFilter( + 'md2html', + array($this, 'markdownToHtml'), + array('is_safe' => array('html')) + ), + ); + } + + public function markdownToHtml($content) + { + return $this->parser->toHtml($content); + } + + public function getName() + { + return 'app_extension'; + } + } + + +最後に、新しいサービスを定義して、このTwigエクステンションをアプリケーションで利用できるようにします。(このサービスは開発するコードの中では利用しないので、サービス名は何でも構いません) + +.. code-block:: yaml + + # app/config/services.yml + services: + app.twig.app_extension: + class: AppBundle\Twig\AppExtension + arguments: ["@markdown"] + tags: + - { name: twig.extension } + + +.. _`Twig`: http://twig.sensiolabs.org/ +.. _`Parsedown`: http://parsedown.org/ +.. _`Twig global variables`: http://symfony.com/doc/master/cookbook/templating/global_variables.html +.. _`override error pages`: http://symfony.com/doc/current/cookbook/controller/error_pages.html +.. _`render a template without using a controller`: http://symfony.com/doc/current/cookbook/templating/render_without_controller.html From 4ca4292aa0d105e1d33338e54f78ecb51e7c488e Mon Sep 17 00:00:00 2001 From: 77web Date: Fri, 24 Oct 2014 00:05:16 +0900 Subject: [PATCH 4/7] [best practice] add configuration.rst --- best_practices/configuration.rst | 164 +++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 best_practices/configuration.rst diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst new file mode 100644 index 0000000..969fcdd --- /dev/null +++ b/best_practices/configuration.rst @@ -0,0 +1,164 @@ +設定 +============= + +設定は、大抵、アプリケーション内の別々の部分(例えば、インフラとユーザー権限のような)と別々の環境 +(開発環境、プロダクション環境)に関連しています。 +そこで、 Symfony はアプリケーションの設定を3つの部分に分けて考えるようにしています。 + +インフラに関係する設定 +------------------------------------ + +.. best-practice:: + + インフラに関係する設定オプションは ``app/config/parameters.yml`` ファイルに定義しましょう。 + +デフォルトの ``parameters.yml`` ファイルはそうなっており、データベースとメールサーバーの設定を定義しています。 + +.. code-block:: yaml + + # app/config/parameters.yml + parameters: + database_driver: pdo_mysql + database_host: 127.0.0.1 + database_port: ~ + database_name: symfony + database_user: root + database_password: ~ + + mailer_transport: smtp + mailer_host: 127.0.0.1 + mailer_user: ~ + mailer_password: ~ + + # ... + + +このオプションは ``app/config/config.yml`` では定義されていません。というのも、アプリケーションの振る舞いに +全く関係がないからです。 +つまり、アプリケーションは、オプションが正しく設定されている限りでは、データベースの場所やユーザー名や +パスワードに関心を持たないのです。 + +デフォルトのパラメータ +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. best-practice:: + + アプリケーションの全てのパラメータを ``app/config/parameters.yml.dist`` に定義しましょう。 + +バージョン2.3以降、 Symfony には ``parameters.yml.dist`` という設定ファイルが含まれており、アプリケーションの +設定で使われるデフォルトのパラメーターリストが保存されています。 + +新しい設定パラメータを定義したなら、このファイルにもそのパラメータを追加して、バージョン管理に反映させましょう。 +開発者がプロジェクトをアップデートした時やサーバーにデプロイした時に、 デフォルトの ``parameters.yml.dist`` と +ローカルの ``parameters.yml`` の差分の有無を Symfony が自動的に調べます。 +もし差分があれば、 Symfony は新しいパラメータの値を尋ね、その値を ``parameters.yml`` に追記します。 + +アプリケーションに関する設定 +--------------------------------- + +.. best-practice:: + + アプリケーションの振る舞いに関する設定は ``app/config/config.yml`` に定義しましょう。 + + +``config.yml`` ファイルには、メール通知の送信元や `feature toggles`_ のような、アプリケーションの振る舞いを +変える設定が含まれます。 +このようなオプション値を ``parameters.yml`` ファイルに定義してしまうと、サーバーごとに変える必要はない設定まで +サーバーごとに設定しなかればならなくなります。 + +``config.yml`` に定義されている設定オプションは、大抵、 `execution environment`_ によって変わります。 +そこで、 Symfony には ``app/config/config_dev.yml`` と ``app/config/config_prod.yml`` があり、環境ごとに +別の値を設定できるようになっているのです。 + +定数か、設定オプションか +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +アプリケーションの設定をするときに最もありがちなミスは、ページ分割の1ページあたりの件数のような、滅多に +変更しない値をオプションとして定義することです。 + +.. best-practice:: + + 滅多に変更しないオプションは定数として定義しましょう。 + +設定オプションを定義する伝統的な方法の結果として、多くの Symfony アプリケーションで下のようなオプションが +定義されています。ブログホームページに表示する投稿の数を制御するオプション値です。 + +.. code-block:: yaml + + # app/config/config.yml + parameters: + homepage.num_items: 10 + +この類いのオプションを最後に変更したのはいつだったか自問してみると、おそらく *一度も変更したことがない* という +答えが出るでしょう。変更しない設定を設定オプションにするのは無駄です。このような値は定数として定義するのが良い +でしょう。 +例えば、 ``Post`` エンティティの ``NUM_ITEMS`` 定数として定義するのです。 + +.. code-block:: php + + // src/AppBundle/Entity/Post.php + namespace AppBundle\Entity; + + class Post + { + const NUM_ITEMS = 10; + + // ... + } + +定数として定義する方式の主なメリットは、値をアプリケーション内のどこでも利用できることです。パラメータとして +定義してしまうと、 Symfony のDIコンテナにアクセスできる場所でしか値を利用できませんでした。 + +例えば、定数は ``constant()`` ヘルパーを利用して Twig テンプレートの中でも使うことができます、 + +.. code-block:: html+jinja + +

+ 最新の投稿から {{ constant('NUM_ITEMS', post) }} 件表示しています。 +

+ +コンテナのパラメータを取得できない Doctrine のエンティティやレポジトリからも値を利用できます。 + +.. code-block:: php + + namespace AppBundle\Repository; + + use Doctrine\ORM\EntityRepository; + use AppBundle\Entity\Post; + + class PostRepository extends EntityRepository + { + public function findLatest($limit = Post::NUM_ITEMS) + { + // ... + } + } + +定数で設定を定義することの唯一の弱点として、テストの時に簡単に値を上書きすることができないので、注意してください。 + +セマンティックな設定(利用してはいけません) +-------------------------------------------- + +.. best-practice:: + + バンドルの設定をセマンティックなDI設定として定義しないようにしましょう。 + +`How to Expose a semantic Configuration for a Bundle`_ で説明されているように、 Symfony のバンドルには設定の方法が +2つあります。 ``services.yml`` を使った通常の設定と、特別な ``*Extension`` クラスを使ったセマンティックな設定です。 + +セマンティックな設定は強力で、設定項目のバリデーションのような役立つ機能もありますが、セマンティック設定を定義する +のにかかる作業が多すぎて、サードパーティのバンドルとして再利用されないバンドルを作る場合には釣り合いが取れないのです。 + +センシティブなオプションを完全に Symfony の外に追い出す +-------------------------------------------------------- + +データベースの接続情報のようなセンシティブなオプションを設定するときには、 Symfony のプロジェクトの外部に保存し、 +環境変数を利用して読み込む方式も推奨します。 +この方式をどのようにして実現するのかについては、 `How to Set external Parameters in the Service Container`_ を +参照してください。 + +.. _`feature toggles`: http://en.wikipedia.org/wiki/Feature_toggle +.. _`execution environment`: http://symfony.com/doc/current/cookbook/configuration/environments.html +.. _`constant() function`: http://twig.sensiolabs.org/doc/functions/constant.html +.. _`How to Expose a semantic Configuration for a Bundle`: http://symfony.com/doc/current/cookbook/bundles/extension.html +.. _`How to Set external Parameters in the Service Container`: http://symfony.com/doc/current/cookbook/configuration/external_parameters.html From 55a673b7361ddfb6fa99e84fb3892ecb5989c230 Mon Sep 17 00:00:00 2001 From: 77web Date: Fri, 24 Oct 2014 00:07:42 +0900 Subject: [PATCH 5/7] [best practice] add business-logic.rst --- best_practices/business-logic.rst | 328 ++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 best_practices/business-logic.rst diff --git a/best_practices/business-logic.rst b/best_practices/business-logic.rst new file mode 100644 index 0000000..106d265 --- /dev/null +++ b/best_practices/business-logic.rst @@ -0,0 +1,328 @@ +アプリケーションのビジネスロジックを整理する +============================================== + +コンピュータのソフトウェアの世界では、データがどのように作られ、表示され、保存され、変更されるかを決める +現実世界のビジネスルールを実装したプログラムを **ビジネスロジック** あるいはドメインロジックと呼びます。 +( `完全な定義`_ を読む) + +Symfony のアプリケーションでは、ビジネスロジックは、フレームワーク(例えばルーティングやコントローラの +ような)に依存せず、自由に書くことができます。 +サービスとして使われる、ドメインのクラス・ Doctrine のエンティティ・通常の PHP のクラスは、ビジネスロジック +の一例です。 + +ほとんどのプロジェクトにおいて、開発者は全てを ``AppBundle`` に置くべきです。AppBundleの中では、ビジネス +ロジックを整理するための、どんなディレクトリでも作ることができます。 + +.. code-block:: text + + symfoy2-project/ + ├─ app/ + ├─ src/ + │ └─ AppBundle/ + │ └─ Utils/ + │ └─ MyClass.php + ├─ vendor/ + └─ web/ + +クラスをバンドルの外に置く +-------------------------------------- + +しかし、ビジネスロジックをバンドルの中に入れておく技術的な理由は何もありません。 ``src`` ディレクトリの +下に好きな名前空間を定義して、クラスをそこに置くこともできます。 + +.. code-block:: text + + symfoy2-project/ + ├─ app/ + ├─ src/ + │ ├─ Acme/ + │ │ └─ Utils/ + │ │ └─ MyClass.php + │ └─ AppBundle/ + ├─ vendor/ + └─ web/ + +.. tip:: + + ``AppBundle`` を使うことをお勧めするのは簡単さのためです。バンドルの中に必要なものと + バンドルの外でも問題ないものがよく理解できている場合は、クラスを ``AppBundle`` の外に + 置いても構いません。 + +サービス:名付けと形式 +--------------------------- + +ブログアプリケーションに、 "Hello World" のような投稿タイトルを "hello-world" のようなスラグに変換する +ユーティリティが必要だとします。スラグは、投稿のURLの一部として使われます。 + +新しい ``Slugger`` クラスを ``src/AppBundle/Utils/`` ディレクトリの配下に作り、下記のような ``slugify()`` +メソッドを実装してみましょう。 + +.. code-block:: php + + // src/AppBundle/Utils/Slugger.php + namespace AppBundle\Utils; + + class Slugger + { + public function slugify($string) + { + return preg_replace( + '/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string))) + ); + } + } + +次に、このクラスのための新しいサービスを定義します。 + +.. code-block:: yaml + + # app/config/services.yml + services: + # サービス名は短くしましょう + slugger: + class: AppBundle\Utils\Slugger + +伝統的に、サービスの名前は、衝突を避けるためにクラス名とクラスの場所を組み合わせたものでした。 +そうすると、このサービスは ``app.utils.slugger`` と呼ばれる *はず* です。しかし、短い名前を使うことで、 +コードの読みやすさと使いやすさは向上するでしょう。 + +.. best-practice:: + + アプリケーションのサービス名は可能な限り短くしましょう。一単語になるのが理想的です。 + +これで ``AdminController`` のようなどんなコントローラーからでも slugger を利用できるようになりました。 + +.. code-block:: php + + public function createAction(Request $request) + { + // ... + + if ($form->isSubmitted() && $form->isValid()) { + $slug = $this->get('slugger')->slugify($post->getTitle())); + $post->setSlug($slug); + + // ... + } + } + +サービス定義:YAML形式 +-------------------------- + +前のセクションでは、 YAML がサービスを定義するのに使われていました。 + +.. best-practice:: + + サービスを定義するときは YAML 形式を使いましょう。 + +これには異論があるでしょうが、経験上、開発者の間では YAML と XML が半々で使われており、ほんの少し YAML +のほうが好まれています。 +どちらの形式も機能は同じなので、どこまでも個人の好みの問題です。 + +新人にもわかりやすく、シンプルな YAML をお勧めしますが、好きな形式を使って構いません。 + +サービス定義:クラス名をパラメータにしない +------------------------------------------- + +前の例で、サービスを定義するとき、クラス名をパラメータとして定義していないことにお気付きかもしれません。 + +.. code-block:: yaml + + # app/config/services.yml + + # クラス名をパラメータにしてサービスを定義 + parameters: + slugger.class: AppBundle\Utils\Slugger + + services: + slugger: + class: "%slugger.class%" + +この使い方は煩雑で、アプリケーションのサービスには全く必要ありません。 + +.. best-practice:: + + アプリケーションのサービスクラス名をパラメータとして定義するのは止めましょう。 + +この使い方はサードパーティのバンドルから誤って取り入れられたものです。 Symfony がサービスコンテナ機能を +実装したとき、開発者の中にはこのテクニックによってサービスを上書きできるようにした人もいました。 +しかし、クラス名を変更しただけでサービスを上書きするのは非常に稀なユースケースです。というのも、大抵の場合、 +新しいサービスには、上書きされるサービスとは違うコンストラクタ引数があるからです。 + +永続化レイヤーを利用する +------------------------- + +Symfony は、 各 HTTP リクエストに対する HTTP レスポンスを作ることだけを担当する HTTP のフレームワークです。 +そのため、 Symfony は永続化レイヤー(データベースや外部API)にアクセスする方法を提供していません。開発者は +好きなライブラリやデータ保存方法を選ぶことができます。 + +実際には、 Symfony アプリケーションの多くは `Doctrine`_ に依存しており、エンティティやレポジトリを +使ってモデルを定義しています。 +ビジネスロジックと同じく、 Doctrine のエンティティも ``AppBundle`` に配置すると良いでしょう。 + +一例として、サンプルのブログアプリケーションで定義した3つのエンティティがあります。 + +.. code-block:: text + + symfony2-project/ + ├─ ... + └─ src/ + └─ AppBundle/ + └─ Entity/ + ├─ Comment.php + ├─ Post.php + └─ User.php + +.. tip:: + + もちろん、独自の名前空間で ``src/`` の直下にエンティティを置くこともできます。 + +Doctrine のマッピング +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Doctrine のエンティティは、データベースに保存することができるプレーンな PHP オブジェクトです。 +Doctrine は、クラスに対して定義されたマッピングメタデータによってエンティティを扱います。マッピングメタデータ +を定義するには YAML, XML, PHP, アノテーション形式が利用できます。 + +.. best-practice:: + + Doctrine エンティティのマッピングにはアノテーションを使いましょう。 + +アノテーションは、マッピングを定義したり探したりするのに、現在のところ最も便利で素早く使える形式です。 + +.. code-block:: php + + namespace AppBundle\Entity; + + use Doctrine\ORM\Mapping as ORM; + use Doctrine\Common\Collections\ArrayCollection; + + /** + * @ORM\Entity + */ + class Post + { + const NUM_ITEMS = 10; + + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="string") + */ + private $title; + + /** + * @ORM\Column(type="string") + */ + private $slug; + + /** + * @ORM\Column(type="text") + */ + private $content; + + /** + * @ORM\Column(type="string") + */ + private $authorEmail; + + /** + * @ORM\Column(type="datetime") + */ + private $publishedAt; + + /** + * @ORM\OneToMany( + * targetEntity="Comment", + * mappedBy="post", + * orphanRemoval=true + * ) + * @ORM\OrderBy({"publishedAt" = "ASC"}) + */ + private $comments; + + public function __construct() + { + $this->publishedAt = new \DateTime(); + $this->comments = new ArrayCollection(); + } + + // getters and setters ... + } + +全てのメタデータ定義形式に同じ機能があり、何度も書いたようにどの形式を使うかは開発者の自由です。 + +データフィクスチャー +~~~~~~~~~~~~~~~~~~~~~~~ + +Symfony にはデフォルトではデータフィクスチャー機能は存在しないため、フィクスチャーを扱うためには下記のコマンドを +実行して DoctrineFixturesBundle をインストールする必要があります。 + +.. code-block:: bash + + $ composer require "doctrine/doctrine-fixtures-bundle" + +バンドルを ``AppKernel.php`` で有効化します。その際、 ``dev`` 環境と ``test`` 環境だけにしてください。 + +.. code-block:: php + + use Symfony\Component\HttpKernel\Kernel; + + class AppKernel extends Kernel + { + public function registerBundles() + { + $bundles = array( + // ... + ); + + if (in_array($this->getEnvironment(), array('dev', 'test'))) { + // ... + $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(), + } + + return $bundles; + } + + // ... + } + + +単純さを保つために、 `フィクスチャークラス`_ は *1つ* だけ作ることをおすすめします。クラスが大きくなりすぎる +場合はもっとたくさんのフィクスチャークラスを作っても構いません。 + +少なくとも1つのフィクスチャークラスがあり、データベースへのログイン情報が正しく設定されているのを確認したら、 +下記のコマンドを実行するとフィクスチャーを読み込ませることができます。 + +.. code-block:: bash + + $ php app/console doctrine:fixtures:load + + Careful, database will be purged. Do you want to continue Y/N ? Y + > purging database + > loading AppBundle\DataFixtures\ORM\LoadFixtures + + +コーディング規約 +------------------ + +Symfonh のソースコードは、 PHP コミュニティで定められた `PSR-1`_ と `PRS-2`_ のコーディング規約に +従っています。詳しくは `Symfonyのコーディング規約`_ を読んでください。 +または、`PHP-CS-Fixer`_ コマンドを使うこともできます。PHP-CS-Fixerはコードベース全体のコーディング規約を +ほんのの数秒で修正することができるコマンドラインツールです。 + +.. _`完全な定義`: http://en.wikipedia.org/wiki/Business_logic +.. _`Toran Proxy`: https://toranproxy.com/ +.. _`Composer`: https://getcomposer.org/ +.. _`MVCアーキテクチャ`: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller +.. _`Doctrine`: http://www.doctrine-project.org/ +.. _`フィクスチャークラス`: http://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html#writing-simple-fixtures +.. _`PSR-1`: http://www.php-fig.org/psr/psr-1/ +.. _`PSR-2`: http://www.php-fig.org/psr/psr-2/ +.. _`Symfonyのコーディング規約`: http://symfony.com/doc/current/contributing/code/standards.html +.. _`PHP-CS-Fixer`: https://github.com/fabpot/PHP-CS-Fixer From 9a74cc5665cfc5490ea46cfedaf2b15243a45664 Mon Sep 17 00:00:00 2001 From: 77web Date: Tue, 4 Nov 2014 23:22:19 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[best=20practice]=20"traditionaly"=20?= =?UTF-8?q?=E3=81=AE=E8=A8=B3=E8=AA=9E=E3=82=92web-assets=E3=81=A8?= =?UTF-8?q?=E7=B5=B1=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- best_practices/templates.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/best_practices/templates.rst b/best_practices/templates.rst index f931f4b..b62e5e7 100644 --- a/best_practices/templates.rst +++ b/best_practices/templates.rst @@ -21,7 +21,7 @@ TwigはSymfonyでのデフォルトのテンプレート言語で、PHP以外も アプリケーションのテンプレートファイルは、全て ``app/Resources/views`` ディレクトリに置きましょう。 -伝統的に、Symfonyの開発者達はアプリケーションのテンプレートファイルを、それぞれのバンドルの ``Resources/views`` ディレクトリに保存してきました。 +慣習的に、Symfonyの開発者達はアプリケーションのテンプレートファイルを、それぞれのバンドルの ``Resources/views`` ディレクトリに保存してきました。 そして、その場所を参照する仮想的な名前を使っていました(例えば ``AcmeDemoBundle:Default:index.html.twig`` ) しかし、アプリケーションで使用するテンプレートについては、 ``app/Resources/views/`` ディレクトリに保存するほうが便利なのです。 From 87fa9043a4e54350dd14304f9a6c37ef6b44b886 Mon Sep 17 00:00:00 2001 From: 77web Date: Wed, 5 Nov 2014 00:02:07 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[best=20practice]=20business-logic.rst=20?= =?UTF-8?q?=E3=81=AE=E4=B8=8D=E8=A6=81=E3=81=AA=E3=83=AA=E3=83=B3=E3=82=AF?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4(fixes=20#324)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- best_practices/business-logic.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/best_practices/business-logic.rst b/best_practices/business-logic.rst index 106d265..d233d69 100644 --- a/best_practices/business-logic.rst +++ b/best_practices/business-logic.rst @@ -317,9 +317,6 @@ Symfonh のソースコードは、 PHP コミュニティで定められた `PS ほんのの数秒で修正することができるコマンドラインツールです。 .. _`完全な定義`: http://en.wikipedia.org/wiki/Business_logic -.. _`Toran Proxy`: https://toranproxy.com/ -.. _`Composer`: https://getcomposer.org/ -.. _`MVCアーキテクチャ`: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller .. _`Doctrine`: http://www.doctrine-project.org/ .. _`フィクスチャークラス`: http://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html#writing-simple-fixtures .. _`PSR-1`: http://www.php-fig.org/psr/psr-1/