Symfony(PHP) - user000422/0 GitHub Wiki
MVCアーキテクチャ。
非常に堅牢であり大規模開発の実績も多く、Webアプリケーション開発に必要な機能は全て備えている。
CakePHP
Laravel
もSymfony
を基に作られている。
Symfonyバージョンについて(公式) https://symfony.com/releases
バージョン | サポート終了 | PHP条件 | 長期サポート版 |
---|---|---|---|
4.4 | 2023/11 | 7.1 以上 | ○ |
5.4 | 2025/11 | 7.2 以上 | ○ |
6.4 | 2027/11 | 8.1 以上 | ○ |
公式 SymfonyBook 日本語有(超参考になる)https://symfony.com/book
公式 ドキュメント(特に参考になる)https://symfony.com/doc/current/index.html
2015年Symfonyエンジニア「Symfony公式ドキュメントの中で、The Symfony Bookと並んで重要なのがSymfony Best Practicesです。」
2015年Symfonyエンジニア「Symfony Best Practicesは、Symfony Wayを強制するものではありませんが、Symfonyらしい設計・実装方法のヒントになります。」
構成公開 https://github.com/ttskch/symfony-example-app/tree/tagged
Formでやり取りするデータはデータ管理クラス(DTO)に設定すべきで、データ管理クラスはバリデーションチェックの設定が可能
■DI(DependencyInjection)依存性の注入
引数でインスタンス(new XXX のアレ)を受け取るイメージ、メソッド内部でnewしなくてよくなる。
Symfonyには、Service ContainerというDIコンテナが初めから導入されて、とても簡単にDIをすることができます。
読んでおくこと https://qiita.com/ippey_s/items/2e279486e1114272a570
読んでおくこと https://engineering.otobank.co.jp/entry/2017/06/13/191624
# 一般的
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
chmod +x /usr/local/bin/composer
cd /var/www/html
# Symfonyプロジェクトを作成
composer create-project symfony/webapp-pack sample-symfony-app # 検証は行っていない
# 下記のような「website-skeleton」と「skeleton」は旧コマンド
# composer create-project symfony/website-skeleton sample-project
Symfony 6.1
パス | 重要 | 説明 |
---|---|---|
bin | ||
config | 設定 | |
migrations | ※昔はなかった? | |
public | ○ | ルート |
src | ○ | プログラム ソースコード |
templates | ○ | テンプレート twig |
tests | テスト関連 | |
translations | 国際化対応関連 | |
var | ログ キャッシュ | |
vendor |
パス | 説明 |
---|---|
.env |
環境設定(DB) |
src/Controller |
Controller |
src/Entity |
データ管理クラス(DTOのような) |
assets |
css ※composer require symfony/webpack-encore-bundle で導入 |
# アプリケーション起動 プロジェクトのディレクトリで行うこと
php -S localhost:8080 -t public # 2023/11/20 RHEL9 Symfony6
symfony server:start # 2023/11/20 RHEL9 Symfony6 なぜか使えなかった
# バージョン確認 プロジェクトのディレクトリで行うこと
bin/console -V
# キャッシュを削除
php bin/console cache:clear
# Controller作成(Symfony4構文)
# テンプレートも作成される
php bin/console make:controller SampleController
■基本型
アノテーションによるルート … @Route([URLでアクセスするパス部分], name=[割り当てる名称])
AbstractControllerの継承 … 継承するのが一般的、便利な機能を備えている。
ビジネスロジックは記述しないこと。
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
class SampleController extends AbstractController
{
// ルーティング Symfony6構文
#[Route('/sample', name: 'app_sample')]
public function index(Request $request)
{
// request->get POST値を取得
$sample_name = $request->request->get('sample_name');
$sample_name = $request->request->get('sample_name', 'default_value'); // 空の場合のデフォルト値を設定
// request 全て取得
$sample_requests = $request->query->all();
// request->get->has POST値の存在チェック
if($request->request->has('sample_name')){
// POST値が存在する
}
// render テンプレートを読み込む ※よく使う
// 第一引数: テンプレートパス(テンプレートパスは「templates/」をカレント)
// 第二引数: テンプレートへ値を渡す
return $this->render('sample/index.html.twig', [
'controller_name' => 'SampleController',
]);
}
}
ルート … アドレスと処理との関連付け
アノテーションによるルーティングと、ルート設定ファイルによるルーティングの2種類ある。
アノテーションによるルーティングがベストプラクティスとされている(Symfony2より)
ルート設定ファイル config/routes.yaml
# path : パスの指定(URLのドメインより後ろのパス)
# controller: コントローラーとアクションの指定
sample:
path: /sample
controller: App/Controller/SampleController::index
構成 : ベーステンプレート
+ 部品テンプレート
+ メインテンプレート(画面ごとの)
Symfony標準のテンプレートエンジン。非常に分かりやすいシンプルな構文。
<form>
は直接記述しないこと(「Symfony Forms」機能の利用を推奨)
タグ … {% %}
<!-- ベーステンプレート base.html.twig -->
<!-- block ベーステンプレートを継承するテンプレートが定義する区画 -->
{% block sample %}
{% endblock %}
<!-- extends 別のテンプレートを継承する -->
{% extends 'base.html.twig' %}
<!-- include 部品の継承(ベースほど大きくないテンプレートの継承) -->
{% include '/sample/sample.html.twig' %}
<!-- block ベーステンプレートの定義に対応しHTMLを定義 -->
{% block sample %}
<!-- 値を埋め込む -->
<p> {{sample_value}} </p>
{% endblock %} <!-- block 終了 -->
<!-- if -->
{% if flg %} <!-- 例では変数「flg」の真偽を検査 -->
<span> Hello </span>
{% endif %} <!-- if 終了 -->
<!-- for -->
{% for item in data %}
<p> {{item.name}} </p>
{% endfor %}
<!-- 変数 -->
{% set result = 'hello' %} <!-- 文字列を設定する場合 -->
{% set result = sample_value %} <!-- 変数を設定する場合 -->
<!-- 画像関連 -->
<!-- asset publicをカレントとする -->
<img src="{{ asset('images/sample.jpg') }}" alt="">
<img src="{{ asset('images/' ~ sample_val ~ '.jpg') }}" alt=""> <!-- 変数を組み込む場合 「~」が文字結合 -->
<!-- HTMLタグとして扱う -->
{{ sample_tab|raw }} <!-- 変数には何かしらのHTMLタグ文字列が代入されている -->
順序 エンティティ作成
→ マイグレーション作成
→ マイグレーションの適用
エンティティの変更を行ったら → マイグレーション作成
→ マイグレーションの適用
※エンティティを作成しマイグレーションを行うと既存のDBは更新されます(確実に意図しない構成になります)
非推奨コマンド doctrine:mapping:import
※2019年より
■データベースの設定
.env
… データベースの設定ファイル。
DATABASE_URL="postgresql://127.0.0.1:5432/db?serverVersion=14&charset=utf8"
■エンティティファイルの作成
php-mysqlnd.x86_64
のインストールが必須
エンティティクラス名はテーブル名をもとにした名前にすること
下記コマンドはSymfony4~6対応コマンド
項目 | コマンド |
---|---|
エンティティファイルの作成 | php bin/console make:entity |
マイグレーションファイルの作成 | php bin/console make:migration |
マイグレーションの適用 | php bin/console doctrine:migrations:migrate |
エンティティファイル作成時のウィザード回答は下記公式を参考
https://symfony.com/doc/current/doctrine.html#creating-an-entity-class
■エンティティの削除方法
エンティティクラスを削除 rm src/Entity/SampleEntity.php
マイグレーションファイルの作成 … php bin/console make:migration
マイグレーションの適用 … php bin/console doctrine:migrations:migrate
■エンティティクラスの構成例(色々なテクニックを紹介)
#[ORM\Entity(repositoryClass: SampleTableRepository::class)]
#[ORM\Table(name: "sample_table")] // テーブル名の設定
#[ORM\Index(name: "sample_idx", fields: ["sample_id", "sample_date"])] // インデックスの設定(複数カラム指定はカンマ区切り)
class SampleTable
{
// デフォルト値「0」を設定する例
#[ORM\Column(type: Types::SMALLINT, options:['default' => 0])]
private ?int $sample_flg = 0;
// デフォルト値「現在時刻」を設定する例 : CURRENT_TIMESTAMP
#[ORM\Column(type: Types::DATETIME_MUTABLE, options: ["default" => "CURRENT_TIMESTAMP"])]
private ?\DateTimeInterface $update_date;
// 主キーにする場合 [ORM\Id]
// 複数カラムに対して設定可
#[ORM\Id]
private ?int $sample_col = null;
// 外部キー
// JoinColumn name:このテーブルの外部キーを設定するカラム
// JoinColumn referencedColumnName:参照先のカラム
#[ORM\ManyToOne(targetEntity: TestTable::class)]
#[ORM\JoinColumn(name: "sample_col", referencedColumnName: "test_col")]
private $testCol;
}
■既存テーブルからエンティティを作成(要確認 要注意)
事前に composer require doctrine/annotations
移動 … cd [プロジェクトディレクトリ]
エンティティファイルの作成 … php bin/console doctrine:mapping:convert --from-database annotation ./src/Entity
作成したエンティティクラスを手動で編集 「namespace App\Entity;」を上部に記述する
getter/setter等を追加 … php bin/console make:entity --regenerate App
パス : src/Service
構成 : Service
+ Controller
+ services.yaml
Service … ビジネスロジックをまとめたクラス
Service Container … サービスをインスタンス化するときに、必要とする定数や他のサービスのオブジェクトを注入したりと、依存関係を解決する役割を担ってくれます。
https://symfony.com/doc/current/service_container.html
// Service Class : src/Service/SampleService.php
namespace App\Service;
class SampleService
{
public function getSample(): string
{
return 'hello';
}
}
// Controller
use App\Service\SampleService;
public function new(SampleService $sampleService): Response
{
// Serviceを呼び出す
$result = $sampleService->getSample();
}
# config/services.yaml
# デフォルトでサービス自動登録が有効になっている。
Form支援機能
https://symfony.com/doc/current/forms.html
必須セット : コントローラー(Form制御)
+ テンプレート(Form表示)
+ データ管理クラス(DTO)
// Controller 関数外コード省略
public function index()
{
// データ管理クラス インスタンス化
$sample = new Sample();
// createFormBuilder FormBuilderを作成 ※このようなメソッドチェーン記述が一般的
// add項目はデータ管理クラスのパラメータと同一にすること
$form = $this->createFormBuilder($sample)
// add Form要素を定義
->add('name', TextType::class) // textbox定義
->add('save', SubmitType::class, ['label' => 'Click']) // submit定義
->getForm();
return $this->render('sample/index.html.twig', [
// createView ビューの生成
'form' => $form->createView(),
]);
}
// Form受け取り側
public function sample()
{
// handleRequest Form情報をハンドリング
$form->handleRequest($request);
$obj = $form->getData(); // Formハンドルから情報を取得(オブジェクト化)
}
<!-- テンプレート formを埋め込む(受け取って表示) -->
<body>
{{form_start(form)}} <!-- Form 開始 <form> -->
{{form_widget(form)}} <!-- Form コントロールの生成 -->
{{form_end(form)}} <!-- Form 終了 </form> -->
</body>
// データ管理クラス
class Sample
{
protected $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
}
エンティティ … DBと連携するため、テーブルの内容をPHPのクラスで定義したもの(表したもの)
リポジトリ … DB操作ユーティリティ
命名(公式)関数名:SQL … show:SELECT、create:INSERT
簡単な操作はController
で行う。複雑な操作はRepositoryクラス
で行う。
■実践用(実際に動かしたコード Symfony6)
// src/Controller/SampleController.php
use App\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
// 割愛
class ProductController extends AbstractController
{
public function show(EntityManagerInterface $entityManager): Response
{
// getRepository リポジトリを取得 第一引数:エンティティクラス
$repository = $entityManager->getRepository(Person::class);
// find SELECT文 第一引数:カラム「id」の条件
$person = $repository->find(10); // カラム「id」が「10」のレコードを取得
// findOneBy SELECT文 第一引数:指定カラムの条件(1つの場合)
$person = $repository->findOneBy(['name' => 'yamada']); // カラム「name」が「yamada」のレコードを取得
// findOneBy SELECT文 第一引数:指定カラムの条件(複数の場合)
$person = $repository->findOneBy([
'name' => 'yamada',
'color' => 'red',
]);
// findall SELECT文 全てのレコードを取得
$person = $repository->findAll();
/*
* 取得したデータの加工
* Entityオブジェクトのためまず分割しなければならない
*/
foreach($person as $row){
// get 取得結果から指定カラムの値を取得
$data[$i] = $person->getName();
$i++;
}
}
}
// 実践 QueryBuilder Repository
// SQL操作を行いたいテーブルのエンティティクラスに対応したRepositoryクラスを使用
// 参考 : https://symfony.com/doc/current/doctrine.html#doctrine-queries
// src/Repository/SampleRepository.php
class SampleRepository extends ServiceEntityRepository
{
// 自由にメソッドを定義する
// (公式例)findAllGreaterThanPrice
public function findSample()
{
// createQueryBuilder
$qb = $this->createQueryBuilder('s') // エイリアスはテーブル名から
->select('s.id') // SELECTはSQLべた書きのイメージ
->where('s.color = :color')
->setParameter('color', 'red')
->groupBy('s.id')
->orderBy('s.id', 'ASC')
->addOrderBy('s.color', 'ASC');
$query = $qb->getQuery();
// return
return $query->execute();
// $query->getResult() を使用する人もいる
// return $query->getResult()
}
}
public function index(Request $request)
{
// Doctrine オブジェクトを取得(共通 基本型)
// getRepository リポジトリを取得 第一引数 : エンティティ
$repository = $this->getDoctrine()->getRepository(Sample::class);
// findall 全レコードのエンティティを取得
$result = $repository->findall();
}
// find 指定IDのエンティティを取得
// 「指定IDのエンティティを取得」は最もよく使われる
// 下記はエンティティの自動フェッチ(DBに関する記述を必要としない アノテーションで管理)
// Routeの{id}がエンティティのIDと連携する
/**
* @Route("/find/{id}", name="find")
* /
public function find(Request $request, Person $person)
{
return xxx;
}
// find by(WHERE sample = 'value')
/**
* @Route("/find", name="name")
* /
public function find(Request $request, Person $person)
{
$form->handleRequest($request);
$findstr = $form->getData()->getfind(); // Form入力値の取得
$repository = $this->getDoctrine()->getRepository(Sample::class); // repositoryの取得
// findBy 一致するレコードを検索 ['カラム' => '条件値']
$result = $repository->findBy(['name' => $findstr]);
}
// Create(Insert)
/**
* @Route("/create", name="create")
* /
public function create(Request $request)
{
$form->handleRequest($request);
$person = $form->getData();
// getManager Managerを取得
$manager = $this->getDoctrine()->getManager();
// persist インスタンスの保存(Insertデータの保存)
$manager->persist($person);
// flush 反映(Insert実行)
$manager->flush();
}
■Native SQL
テーブルに外部キーを設定できない等のプロジェクト制約でQuerybuillderが使用できない場合に。
参考 : https://symfony.com/doc/current/doctrine.html#querying-with-sql
public function findAllGreaterThanPrice(int $price): array
{
$conn = $this->getEntityManager()->getConnection();
$sql = 'SELECT * FROM sample_table WHERE price > :price';
$resultSet = $conn->executeQuery($sql, ['price' => $price]);
return $resultSet->fetchAllAssociative();
}
Entity
+ Controller
Formで定義する方法もあるが、時間がないため割愛。
エンティティ(またはデータ管理クラス)にて、アノテーション(@Assert)で定義。
// エンティティクラス
/**
* @ORM xxxxx
* @Assert\NotBlank() // Null、空のテキストを禁止。
* @Assert\Type(type="string") // データ型指定。
* @Assert\Length(min = 1, max = 10) // 文字数
*/
private sample;
コントローラーにて、バリデーションチェック
// Controller
public function sample(Request $request, ValidatorInterface $validator)
{
$sample = $form->getData();
// validate バリデーションチェック 引数: エンティティ
// 戻り値: エラーデータの配列
$errors = $validator->validate($sample);
if (count($errors) == 0){ /* エラーなしの場合 */ }
}
標準でSymfonyに用意されているクラス。
クラス名 | 説明 |
---|---|
JsonResponse | JSONとデータ連携 |
use Psr/Log/LoggerInterface |
ログ操作 |
■twigでcssを扱いたい(Webpack Encore)
# RHEL9
composer require symfony/webpack-encore-bundle
# yarnの導入
curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo
yum install yarn
yarn install
yarn build
・CSSの追加
assets/app.js
■Session
// 引数の「SessionInterface」が必須
public function index(SessionInterface $session)
{
// remove 指定sessionを削除
$session->remove('sample');
}
参考 : https://symfony.com/doc/current/doctrine/associations.html
フィールドとゲッターセッターを定義する
// SampleTableとSubTableのエンティティリレーション
use App\Entity\SubTable;
// OneToOneの例
#[ORM\OneToOne(targetEntity: SubTable::class)]
private $subTable;
// get set 忘れずに
// querybuilderの例
$qb = $this->createQueryBuilder('a')
->select('a.id, b.name')
// テーブルaで定義した他テーブルを呼び出す, エイリアス, WITH(ON句), ON句の内容
->join('a.subTable', 'b', 'WITH', 'a.id = b.id');
■ResourceNotFoundException(No Root)
Controllerの中に定義の怪しいものがある。
■Compile Error: Cannot declare class Sample, because the name is already in use
namespaceが不正。