PHP
Rails
Symfony
Symfony2
cloud9

RailsユーザーだけどPHP/Symfony触ってみた

More than 3 years have passed since last update.

もうすぐPHPとSymfonyを使うかもしれないので、雰囲気をつかむために触ってみました。PHPからして初めてなので、軽くそこも勉強しつつ。

Symfonyは、フルスタックでMVCなWebアプリケーションフレームワークなんですって。


こんなん作る

最低限のMVCを組み込める以下のようなWebアプリを、RailsとSymfonyで作り比べてみることにしました。

ちなみに私、Railsユーザって言ってもそんなにRails使い込んでるわけではないですが、まあいい比較対象かなと。

コメントを保存するボタン、保存したコメントのリスト表示、全コメントを削除できるリンクのあるアプリです。画面は1つだけ。

rails2.png


Railsで

とりあえずRailsで作ってみました。


環境

Cloud9の初期環境のままです。

Cloud9は、クラウド上で動作するIDEです。ワークスペース1個作ると開発環境が一通り揃ったUbuntu1つもらえるので、とりあえず何か動かしたいときは便利。


  • Ubuntu 14.04.3 LTS

  • ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]

  • Rails 4.2.5


作って動かす

アプリcommenter-rを新規作成、commentsという名前でControllerを生成し、Commentという名前でModelも生成&マイグレーション。

$ rails new commenter-r

$ rails generate controller comments
$ rails generate model Comment content:string
$ bundle exec rake db:migrate

Controllerとルーティングのコードはこんな感じ。


comments_controller.rb

class CommentsController < ApplicationController

# 全Commentをリスト表示
def index
@comment = Comment.new
@comments = Comment.all
end

# コメントをPOST内容から新規作成してindexにリダイレクト
def create
Comment.create(content: comment_params[:content])
redirect_to comments_url
end

# コメントを全削除してindexにリダイレクト
def destroy
Comment.destroy_all
redirect_to comments_url
end

# Strong Parameters用コード

end



routes.rb

Rails.application.routes.draw do

get 'comments', to: 'comments#index'
post 'comments', to: 'comments#create'
delete 'comments', to: 'comments#destroy'

root 'comments#index'
end


画面は1個なので、Viewはこれだけ。


index.html.erb

<h1>コメントリスト</h1>

<%= form_for(@comment) do |f| %>
<%= f.text_field :content %>
<%= f.submit %>
<% end %>

<ui>
<% @comments.each do |comment| %>
<li><%= comment.content %></li>
<% end %>
</ui>

<%= link_to '全て削除', comments_path, method: :delete %>


動作確認。普通はrails sだけど、クラウド上にあるマシンのlocalhostで動かしてもしょうがないので、Cloud9では以下のようなコマンドで起動させます。

$ rails s -p $PORT -b $IP

とりあえずこれで大作アプリ(笑)完成しました。

rails.png


Symfonyで

さあSymfonyでも作るぞ。


PHP…?

そもそもPHPが初めてだったので軽く勉強しました。WikipediaとかQiitaとかの入門記事色々読みました。意外とWikipediaの記事よかったです。

Symfonyのドキュメントも含め、ネット上の記事は古いバージョンのPHP向けの記述も多く、注意が必要です。(現場で使えるPHPのバージョンはまだわからないけど…)Qiitaでは@tsdsanモダンPHPアンチパターン - Qiitaが、過去と現在のPHPの雰囲気をつかめてありがたかったです。

PHPのオレオレ理解。

* javaっぽい見た目

* .の代わりに->

* 変数の頭に$

* ファイルの先頭に<?phpと書くのは鉄の掟。なぜか閉じなくてよい。

* array()は古語。読むのはともかく自分のコードでは[]と書きたい。

* RubyでいうBundler、PHPではComposer

ちょっとSymfony触るだけなので、言語に関してはサラッとすます感じですが、そのうちちゃんと勉強します…


環境

Rails版と同じく、Cloud9で作ることにします。


  • Ubuntu 14.04.3 LTS

  • PHP 5.5.9-1ubuntu4.14 (cli) (built: Oct 28 2015 01:34:46)

  • Symfony version 3.0.3


Symfonyインストール&アプリ新規作成

公式に従い、Symfonyをインストールします。

$ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony

$ sudo chmod a+x /usr/local/bin/symfony

そしてcommenter-sプロジェクトを新規作成。

$ symfony new commenter-s

作ったプロジェクトを起動するrails sに相当するコマンドはphp bin/console server:runなんですが、やはりCloud9で動作させるにはちょっとコツが必要。Cloud9の公式にSymfonyプロジェクトの起動方法が載ってます。

raw.png

とりあえず動きました。


バンドル作る

Symfonyで特徴的なのはバンドルという概念です。

Railsでは生成したアプリケーションの雛型にガリガリコードを書いてく感じですが、Symfonyではアプリケーション上にバンドルという再利用可能なモジュールを作り、そこにModel/View/Controllerなどのコードを書きます。Symfonyの本体やプラグインもすべてバンドルで出来ていて、フラクタルな感じがいいですね。

バンドルの命名規約のベストプラクティスも公式にあって、{ベンダー名}/{機能名}Bundleがおすすめらしいです。今回はOreore/CommenterBundleという名前にしました。

$ php bin/console generate:bundle --namespace=Oreore/CommenterBundle

このコマンドでバンドルを生成してくれます。対話的に何やら聞かれますが、今回は全部デフォルトで作りました。


ルーティング

ルーティングは、Railsと同じようにルーティング用の定義ファイルに記述することもできて、日本Symfonyユーザー会のドキュメントではそのようになっています。

でも今イケてるのは、ルーティング先となるControllerのメソッドに、ルーティング元のパスとメソッドをアノテーションで書くスタイルらしいです。

こんな感じ。


src/Oreore/CommenterBundle/Controller/DefaultController.ph

class DefaultController extends Controller

{
/**
* @Route("/", name="index")
* @Method("GET")
*/

public function indexAction() {}

/**
* @Route("/")
* @Method("POST")
*/

public function createAction(Request $request) {}

/**
* @Route("/destroy", name="destroy")
*/

public function destroyAction() {}
}


ルートとルーティング先メソッドを並べて見れるため、いい感じだと思います。

ルーティングの一覧は、以下のコマンドで確認できます。

$ php bin/console debug:rou


Model

RailsでActiveRecordに当たるのが、SymfonyではDoctrineというモジュールです。

初めにEntityクラスを作り、これを元にしてDBテーブルを生成できます。

Entityは手書きでもつくれますが、以下のコマンドで対話的にEntityを作成できます。

text型のcontentフィールドを持つ、Commentクラスを作ってみます。

$ php bin/console doctrine:generate:entity

...
# Entity名
The Entity shortcut name: OreoreCommenterBundle:Comment
# カラム名
New field name (press <return> to stop adding fields): content
# カラム型
Field type [string]: text
...

これでcontentフィールドとそのgetter/setterをもつCommentクラスがEntityフォルダに生成されました。各クラスメンバには、ORM用のアノテーションが付加されています。

次のコマンドで、作成済みのEntityクラスからデフォルトのDBエンジン(MySQL)のDBテーブルを生成してくれます。

$ php bin/console doctrine:schema:update --force


Controller

コントローラのindex用メソッドindexActionはこんな感じになりました。


src/Oreore/CommenterBundle/Controller/DefaultController.ph

class DefaultController extends Controller

{
/**
* @Route("/", name="index")
* @Method("GET")
* @Template()
*/

public function indexAction()
{
// Viewで使うformをセットアップ
$comment = new Comment();
$form = $this->createFormBuilder($comment)
->add('content', TextType::class)
->add('save', SubmitType::class, ['label' => 'Create Comment'])
->getForm();

// DBからコメント一覧取得
$repository = $this->getDoctrine()
->getRepository('OreoreCommenterBundle:Comment');
$comments = $repository->findAll();

// Viewに作ったインスタンスを渡す
return [
'form' => $form->createView(),
'comments' => $comments
];
}


基本的にRails版と同じことをやらせてますが、比べると大分コード量増えてますね… ここはまあ設計思想の違いかなと思います。

とにかく短くかけるけど最早Rails語、っていう感じのActiveRecordと違い、Doctrineの場合、ContollerのインスタンスからDoctrine、エンティティをフェッチするためのRepositoryをとってきて、クエリを投げるというインスタンス間の責務が明確です。

それとViewで参照するための変数をreturnしてるのが特徴的かなと思います。テストしやすそうだし、インスタンス変数経由でViewに値を渡すRailsよりも副作用がない感じ。

ちなみにこのようにreturnできるのはアノテーションで@Template()と書いているからで、それがない場合はreturnでレンダリングするHTML文字列を含むResponceインスタンスを返す形になります。それはそれで、HTTPリクエストに対して文字列のレスポンスを返す、というWebアプリケーションの本分が一つのメソッドに集約される形になっていて、割と好きです。

Controllerの残りは、コメント投稿用のcreateActionとコメント全削除用のdestroyActionです。


src/Oreore/CommenterBundle/Controller/DefaultController.ph

class DefaultController extends Controller

{
/**
* @Route("/")
* @Method("POST")
*/

public function createAction(Request $request)
{
// フォームから投稿内容取得
$data = $request->request->all();
$content = $data['form']['content'];

// エンティティインスタンスを作成
$comment = new Comment();
$comment->setContent($content);

// エンティティインスタンスの内容をDBに保存
$em = $this->getDoctrine()->getManager();
$em->persist($comment);
$em->flush();

// indexにリダイレクト
return $this->redirectToRoute('index');
}

/**
* @Route("/destroy", name="destroy")
*/

public function destroyAction()
{
// DBから全件取得
$repository = $this->getDoctrine()
->getRepository('OreoreCommenterBundle:Comment');
$comments = $repository->findAll();

// 取得内容をDBから全件削除
$em = $this->getDoctrine()->getManager();
foreach ($comments as $key => $comment) {
$em->remove($comment);
}
$em->flush();

// indexにリダイレクト
return $this->redirectToRoute('index');
}
}



View & 動作確認

SymfonyのデフォルトのテンプレートエンジンはTwigです。

ERBとかと大体同じで、{{ ... }}で囲うとPHP式の表示、{% ... %}でロジックを書けます。

今回はこんな風になりました。

<h1>コメントリスト</h1>

{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}

<ul>
{% for comment in comments %}
<li>{{ comment.content }}</li>
{% endfor %}
</ul>

<a href="{{ path('destroy') }}">全て削除</a>

form_xxxのところは、Controllerで構築したformをデフォルトの見た目やパラメータでレンダリングしてくれる手続きです。

これで動作確認するとこんな感じになりました。

Rails版とちょっと見た目が違っちゃいましたが、機能的にはほとんど同じです。

symfony.png


感想とか!


  • バンドルという仕組みが面白い。

  • Railsほど、シンプルな記法にこだわってない。

  • アノテーションで出来ることが多い。IDEの恩恵高そう。


参考資料


成果物