どうしてSymfonyを?
既存システムを解析する案件が来る予定だったのでちょこちょこ調べた。
ざっくばらんなメモなので、通して読むよりトピック的に拾う方が正しい。
Symfonyに触れる環境を作る
SymfonyCastsの「Charming Development in Symfony 5」をなぞるのが手取り早い。
「03. Route, Controllers&Responses!」までやると、とりあえず動くようになる。
Symfony解析時の基本的な流れ
-
routing.yml
でルーティング情報を確認する -
Controller
に格納されているXxxController.php
でルーティングに対応する関数を確認する - 関数内の処理を解析する
フォルダパス
Sample\SampleBundle
├─Controller // コントローラ。ルーティングに対応するのはここを参照。
├─DataFixtures // 初期データ等を用意するデータフィクスチャ
│ └─ORM // ORMで実行するデータフィクスチャ
├─DependencyInjection
├─Entity // データを運搬するためのEntity。ORMとのマッピングもここ
├─Form // Entityをごにょごにょする(要調査)
├─Repository // DBとのやりとりする処理を格納
├─Resources
│ ├─config
│ │ ├─config.yml // 設定値を保存するyamlファイル
│ │ ├─routing.yml // [重要]ルーティング情報を保存するyamlファイル
│ │ └─services.yml
│ ├─public
│ │ └─css // CSS
│ └─views // コントローラ名に対応するフォルダ。フォルダ内はtwigファイル。
│ └─AAA // AAAコントローラに対応するtwigファイル
├─Tests // 自動テストに使用するファイ
└─Twig
└─Extensions
登録されているバンドルを確認する
以下のファイルにインストールされているバンドルの情報が記載される。
%アプリケーションのルートディレクトリ%/app/AppKernel.php
-
$bundle
に対してnew
されているものは状態を問わずに使われる -
dev
と付いているものは開発環境で使われる -
test
と付いているものはテスト環境で使われる
バンドルのルーティングを確認する
以下のファイルに使用するバンドルのルーティングを記載したyamlの格納パスが記載される。
%アプリケーションのルートディレクトリ%/app/config/routing.yaml
上記ファイルパスを開いた例が以下である。
SampleBundle:
resource: "@SampleBundle/Resources/config/routing.yml"
prefix: /
resource
に設定されたファイルはファイルパスに直すと以下になる。
%アプリケーションのルートディレクリ%/src/%ネームスペース名%/%バンドル名%/Resources/config/routing.yaml
上記ファイルパスを開いた例が以下である。
SampleBundle_homepage:
pattern: /
defaults: { _controller: SampleBundle:Page:index }
requirements:
_method: GET
SampleBundle_about:
pattern: /about
defaults: { _controller: SampleBundle:Page:about }
requirements:
_method: GET
こちらの例であれば、以下のルーティングが設定されている。
-
/
にGET
でアクセスした際、PageController
のindexAction
を呼び出す -
/about
にGET
でアクセスした際、PageController
のaboutAction
を呼び出す
ルーティング
上記のルーティングの設定にコメントを付与すると以下になる。
SampleBundle_homepage:
pattern: / # URLパターン
defaults: { _controller: SampleBundle:Page:index } # 呼び出すコントローラと関数
requirements:
_method: GET # リクエスト
URLパターンの一部をブラケット({}
)で括るとその部分はプレースホルダーとしてどのような値でもマッチする。
設定例は以下である。
SampleBundle_anyvalue:
pattern: /anyvalue/{value}
defaults: { _controller: SampleBundle:Page:anyvalue }
上記設定では以下がマッチする。
%バンドルに設定したパス%/anyvalue/
%バンドルに設定したパス%/anyvalue/hoge
%バンドルに設定したパス%/anyvalue/foobar
また、上記例ではrequirements
の_method
が省略されているが、その場合、GET
、POST
、PUT
、DELETE
いずれにもマッチする。
URLに含まれる不要なスラッグ
以下のようなルーティングとURLを考える。
SampleBundle_slugsample:
pattern: /{id}/{value}
defaults: { _controller: SampleBundle:Page:slugsample }
requirements:
_method: GET
id: \d+
http://somedomain/appname/1/this_is_a_slug
上記例ではrequirements
にid(=1)
は含まれているが、slug
は含まれていない。
このような場合、URLから内容がわかるため、SEOを期待している。
設定する場合は、登録時にタイトルの非ASCII文字を-
に置き換えてDBに格納すると良い。
テンプレート
基本的にPHP
かtwig
が使われる。
テンプレートの継承とデザインは「3レベル継承(three-level template inheritance)」アプローチが推奨されている。
twig
であればテンプレートの格納先は以下のどちらかになる。
%アプリケーションのルートディレクリ%/app/Resources/views/xxxx.html.twig
%アプリケーションのルートディレクリ%/src/%ネームスペース名%/%バンドル名%/Resources/views/xxxx.html.twig
リンク
twig
を使用している場合のリンクは以下のようにルーティングで設定した名前となる。
{% block navigation %}
<nav>
<a href="{{ path('SampleBundle_homepage') }}">Home</a>
</nav>
{% endblock %}
間違っても以下のようにURLで設定してはいけない。
これはSymfonyのルーティングを無視することになるため、正常に動作しない。
{% block navigation %}
<nav>
<!-- DO NOT USE -->
<a href="/">Home</a>
</nav>
{% endblock %}
EntityとForm
値を格納する最小単位がEntityである。
Entityでは以下のような要素を格納する。
- 変数
- 変数へのアクセサ(変数hogeへのgetHoge/setHoge)
- バリデーション(loadValidatorMetadata)
FormBuilderInterfaceを使用したり、Controllerで設定する際に使用されるのがFormである。
AbstractTypeをextendsしていることがある。
ルーティング設定例
ルーティングを設定するには以下の方法がある。
どちらを軸に設定されているか確認する。
- routes.ymlで設定する
- アノテーションで設定する
routes.yamlで設定する
routes.ymlの配置先は以下の通り。
%プロジェクトのフォルダ%/config/routes.yml
開くとデフォルトで以下のようになっている。
# index:
# path: /
# controller: App\Controller\DefaultController::index
行頭の「#」はコメントアウトを示す。
pathはURLのパスを、controllerは対応するコントローラを示す。
上記のコメントアウトを外した場合、「/」で「%プロジェクトのフォルダ%/src/Controller/DefaultController.php」の「index」が呼ばれることになる。
アノテーションで設定する
パスをアノテーションで関数単位で設定する。
以下の例では「/」で画面に「sample response」と表示される。
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class SampleController
{
/**
* @Route("/")
*/
public function homepage()
{
return new Response('sample response');
}
}
?>
設定されているパスを一覧で表示する
terminalでプロジェクトのフォルダを開き、以下のコマンドを叩く。
$ php bin/console debug:router
以下のように表示される。
----------------------- -------- -------- ------ --------------------------
Name Method Scheme Host Path
----------------------- -------- -------- ------ --------------------------
_preview_error ANY ANY ANY /_error/{code}.{_format}
app_sample_homepage ANY ANY ANY /
----------------------- -------- -------- ------ --------------------------
バリデーション
入力値をDB登録前に検証する。
Entity内に設定することで検証が行われる。
バリデータを使う際は以下のように使う制約をuse命令文で追加する
use Symfony\Component\Validator\Mapping\ClassMetadata;
Doctrine2
SQLではなくDoctrine Query Language(DQL)
を使用する。
ORMとして動作し、各DBとのやりとりを行い、データの永続化に寄与する。
Entity
にporotected
な変数を宣言し、アノテーションでマッピングを行う。
namespace Sample\SampleBundle\Entity;
class Sample
{
protected $id;
protected $value1;
// 後述のgetter/setterは省略
}
namespace Sample\SampleBundle\Entity;
use Doctrine\ORM\Mapping as ORM; // Doctrineを読み込み
//
/**
* @ORM\Entity
* @ORM\Table(name="sample")
*/
class Sample
{
// オートインクリメントするIDの設定
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// 文字列型のvbalue1の設定
/**
* @ORM\Column(type="string")
*/
protected $value1;
}
実際に設定する場合は、インスタンスを生成して値を入れる。
$sample = new Sample();
$sample->setValue1("val1");
// idは自動歳晩なのでset不要
Doctrine2を使用してSQLを流すこともできるが、データベース抽象を使用しないため推奨されない。
SQLを実行したい場合はQueryBuilder
を使用するとよい。
// QueryBuilder以下を表現
// SELECT 'hoge', 'created' FROM SampleBundle ORDER BY 'created' DESC;
$em = $this->getDoctrine()->getManager();
$records = $em->createQueryBuilder()
->select('hoge')
->from('SampleBundle:Sample', 'hoge')
->addOrderBy('hoge.created', 'DESC')
->getQuery()
->getResult();
クエリー実行は以下の理由からコントローラ内で行わないこと。
- アプリケーション内で同一のクエリーを実行する場合、再利用ができない
- コードが重複しする(DRY原則に反する)
- 分離することにより、クエリー単体のテストが実行可能となる
⇒分離する際はDoctrine2 Repositoriesを使用する