[Symfony] 既存のアプリを機能ごとにバンドルに分割したときにやったことまとめ
既存のSymfonyアプリに大きめの機能追加を行うことになり、さすがに(広義の)モノリスでは管理しきれないボリュームになってきたので、既存機能をバンドルに分割しました。
そのときにやったことを備忘録としてまとめておきます。
アプリをバンドルに分割するのはNG?
ちなみに Symfonyのドキュメント には
In Symfony versions prior to 4.0, it was recommended to organize your own application code using bundles. This is no longer recommended and bundles should only be used to share code and features between multiple applications.
という記載があり、アプリを機能ごとにバンドルに分割するのは推奨されない方法のようです。
正しくやるなら、Gitリポジトリごと分けて、各機能を「再利用可能なバンドル」として実装し、メインのアプリに各バンドルをインストールして全体を組み立てることになるのでしょうか🤔(詳しい方いたら教えてください🙏)
そうは言っても現実的には以前のようにアプリ内にバンドルを複数作りたいケースは多いと思うので、今回は非推奨であることには目をつぶっていただいて🙈、教養として読んでもらえたらいいかなと思います😅
src
配下のファイルをバンドルのディレクトリに移動
1. まず、 src
配下に Bundle/バンドル名
というパスでディレクトリを切り、各種ファイルをここに移動します。
Before
.
└── src
├── Command
├── Controller
├── Entity
├── EntityListener
├── Event
├── EventSubscriber
├── Form
├── Repository
├── Security
├── Twig
├── Validator
└── Kernel.php
After
.
└── src
├── Bundle
│ ├── AppBundle
│ │ ├── Command
│ │ ├── Controller
│ │ ├── Entity
│ │ ├── EntityListener
│ │ ├── Form
│ │ ├── Repository
│ │ ├── Security
│ │ ├── Twig
│ │ └── Validator
│ └── FooBundle
│ ├── Controller
│ ├── Entity
│ ├── EntityListener
│ ├── Event
│ ├── EventSubscriber
│ ├── Form
│ ├── Repository
│ ├── Twig
│ └── Validator
└── Kernel.php
ユーザー認証周りや機能に依らない共通部品っぽいものを AppBundle
に、 Foo
機能に関連するものを FooBundle
に振り分けるとよいでしょう。
移動したファイルの namespace
はディレクトリ位置に合わせて適切に変更し、そのクラスを参照している他のファイルの use
文もあわせて変更します。
これが一番大変な作業だと思います。IDEの力を借りつつ頑張りましょう💪
2. バンドルをフレームワークに登録
src/Bundle/AppBundle/AppBundle.php
および src/Bundle/FooBundle/FooBundle.php
を作成します。
<?php
namespace App\Bundle\AppBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
}
<?php
namespace App\Bundle\FooBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class FooBundle extends Bundle
{
}
そしてこれらを config/bundles.php
に登録します。
<?php
return [
// ...
App\Bundle\AppBundle\AppBundle::class => ['all' => true],
App\Bundle\FooBundle\FooBundle::class => ['all' => true],
];
templates
配下のファイルを移動
3. 各バンドルに Resources/views
ディレクトリを作成し、 templates
配下のビューのファイルをそこに移動します。
Before
.
└── templates
├── home
│ ├── index.html.twig
│ ├── show.html.twig
│ ├── new.html.twig
│ └── edit.html.twig
├── user
│ ├── index.html.twig
│ ├── show.html.twig
│ ├── new.html.twig
│ └── edit.html.twig
├── bar
│ ├── index.html.twig
│ ├── show.html.twig
│ ├── new.html.twig
│ └── edit.html.twig
├── baz
│ ├── index.html.twig
│ ├── show.html.twig
│ ├── new.html.twig
│ └── edit.html.twig
└── base.html.twig
After
.
└── src
├── Bundle
│ ├── AppBundle
│ │ :
│ │ :
│ │ ├── Resources
│ │ │ └── views
│ │ │ ├── home
│ │ │ │ ├── index.html.twig
│ │ │ │ ├── show.html.twig
│ │ │ │ ├── new.html.twig
│ │ │ │ └── edit.html.twig
│ │ │ ├── user
│ │ │ │ ├── index.html.twig
│ │ │ │ ├── show.html.twig
│ │ │ │ ├── new.html.twig
│ │ │ │ └── edit.html.twig
│ │ │ └── base.html.twig
│ │ └── AppBundle.php
│ └── FooBundle
│ :
│ :
│ ├── Resources
│ │ └── views
│ │ ├── bar
│ │ │ ├── index.html.twig
│ │ │ ├── show.html.twig
│ │ │ ├── new.html.twig
│ │ │ └── edit.html.twig
│ │ └── baz
│ │ ├── index.html.twig
│ │ ├── show.html.twig
│ │ ├── new.html.twig
│ │ └── edit.html.twig
│ └── FooBundle.php
└── Kernel.php
extends
や include
などで他のテンプレートファイルを参照している箇所がある場合は、以下のようにそのテンプレートファイルがどのバンドルに属しているかを明記する必要があるので、該当箇所すべてを修正します。
- {% extends 'base.html.twig' %}
+ {% extends '@App/base.html.twig' %}
4. テストファイルを移動
各バンドルに Tests
ディレクトリを作成し、 tests
配下のファイルをそこに移動します。
Before
.
└── tests
├── Controller
│ ├── HomeControllerTest.php
│ ├── UserControllerTest.php
│ ├── BarControllerTest.php
│ └── BazControllerTest.php
├── Repository
│ ├── UserRepositoryTest.php
│ ├── BarRepositoryTest.php
│ └── BazRepositoryTest.php
├── fixture
│ ├── Controller
│ │ ├── HomeControllerTest.yaml
│ │ ├── UserControllerTest.yaml
│ │ ├── BarControllerTest.yaml
│ │ └── BazControllerTest.yaml
│ └── Repository
│ ├── UserRepositoryTest.yaml
│ ├── BarControllerTest.yaml
│ └── BazRepositoryTest.yaml
└── bootstrap.php
After
.
├── src
│ ├── Bundle
│ │ ├── AppBundle
│ │ │ :
│ │ │ :
│ │ │ ├── Tests
│ │ │ │ ├── Controller
│ │ │ │ │ ├── HomeControllerTest.php
│ │ │ │ │ └── UserControllerTest.php
│ │ │ │ ├── Repository
│ │ │ │ │ └── UserRepositoryTest.php
│ │ │ │ └── fixture
│ │ │ │ ├── Controller
│ │ │ │ │ ├── HomeControllerTest.yaml
│ │ │ │ │ └── UserControllerTest.yaml
│ │ │ │ └── Repository
│ │ │ │ └── UserRepositoryTest.yaml
│ │ │ └── AppBundle.php
│ │ └── FooBundle
│ │ :
│ │ :
│ │ ├── Tests
│ │ │ ├── Controller
│ │ │ │ ├── BarControllerTest.php
│ │ │ │ └── BazControllerTest.php
│ │ │ ├── Repository
│ │ │ │ ├── BarRepositoryTest.php
│ │ │ │ └── BazRepositoryTest.php
│ │ │ └── fixture
│ │ │ ├── Controller
│ │ │ │ ├── BarControllerTest.yaml
│ │ │ │ └── BazControllerTest.yaml
│ │ │ └── Repository
│ │ │ ├── BarRepositoryTest.yaml
│ │ │ └── BazRepositoryTest.yaml
│ │ └── FooBundle.php
│ └── Kernel.php
└── tests
└── bootstrap.php
プロダクトコード同様、namespaceの変更が必要です。
続けて、 phpunit.xml.dist
を修正します。
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="bin/.phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php"
>
<php>
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<server name="SHELL_VERBOSITY" value="-1" />
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
<server name="SYMFONY_PHPUNIT_VERSION" value="7.5" />
</php>
<testsuites>
- <testsuite name="Project Test Suite">
- <directory>tests</directory>
- </testsuite>
+ <testsuite name="AppBundle">
+ <directory>src/Bundle/AppBundle/Tests</directory>
+ </testsuite>
+ <testsuite name="FooBundle">
+ <directory>src/Bundle/FooBundle/Tests</directory>
+ </testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
</phpunit>
liip/test-fixtures-bundle などを使っている場合は、フィクスチャ定義のYAMLファイル内でもエンティティのFQCNを修正する必要があるので注意しましょう。
- App\Entity\User:
+ App\Bundle\AppBundle\Entity\User:
user1:
email: user1@test.com
plainPassword: password
5. ルーティングを設定
各バンドルに Resources/config/routing.yaml
を追加し、両方のファイルに以下の内容を記述します。
controllers:
resource: ../../Controller/
type: annotation
そして、 config/routes.yaml
に以下を追記します。
app_bundle:
resource: '@AppBundle/Resources/config/routing.yaml'
foo_bundle:
resource: '@FooBundle/Resources/config/routing.yaml'
config/routes/annotations.yaml
に以下のような内容が記述されている場合は、不要なので削除します。
- controllers:
- resource: ../../src/Controller/
- type: annotation
doctrine.yaml
を修正
6. config/packages/doctrine.yaml
でエンティティのマッピング設定を修正します。
doctrine:
orm:
mappings:
App:
is_bundle: false
type: annotation
- dir: '%kernel.project_dir%/src/Entity'
- prefix: 'App\Entity'
+ dir: '%kernel.project_dir%/src/Bundle/AppBundle/Entity'
+ prefix: 'App\Bundle\AppBundle\Entity'
alias: App
+ Foo:
+ is_bundle: false
+ type: annotation
+ dir: '%kernel.project_dir%/src/Bundle/FooBundle/Entity'
+ prefix: 'App\Bundle\FooBundle\Entity'
+ alias: Foo
security.yaml
を修正
7. config/packages/security.yaml
に User
エンティティやGuardクラスのFQCNが書いてあるので、これも修正します。
security:
encoders:
- App\Entity\User:
+ App\Bundle\AppBundle\Entity\User:
algorithm: bcrypt
providers:
app_user_provider:
entity:
- class: App\Entity\User
+ class: App\Bundle\AppBundle\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: lazy
provider: app_user_provider
guard:
authenticators:
- - App\Security\LoginFormAuthenticator
+ - App\Bundle\AppBundle\Security\LoginFormAuthenticator
logout:
path: user_logout
target: user_login
switch_user: true
remember_me:
secret: '%kernel.secret%'
services.yaml
を修正
8. config/services.yaml
にもディレクトリ構造に依存した設定がいろいろあるはずなので、以下のような要領で修正します。
parameters:
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
#
# Controllers
#
- App\Controller\:
- resource: '../src/Controller'
- tags: ['controller.service_arguments']
+ App\Bundle\AppBundle\Controller\:
+ resource: '../src/Bundle/AppBundle/Controller'
+ tags: ['controller.service_arguments']
+
+ App\Bundle\FooBundle\Controller\:
+ resource: '../src/Bundle/FooBundle/Controller'
+ tags: ['controller.service_arguments']
#
# Entity listeners
#
- App\EntityListener\:
- resource: '../src/EntityListener'
- tags: ['doctrine.orm.entity_listener']
+ App\Bundle\AppBundle\EntityListener\:
+ resource: '../src/Bundle/AppBundle/EntityListener'
+ tags: ['doctrine.orm.entity_listener']
+
+ App\Bundle\FooBundle\EntityListener\:
+ resource: '../src/Bundle/FooBundle/EntityListener'
+ tags: ['doctrine.orm.entity_listener']
#
# Twig extensions
#
- App\Twig\AppExtension:
- tags: ['twig.extension']
+ App\Bundle\AppBundle\Twig\AppExtension:
+ tags: ['twig.extension']
まとめ
だいたいこんな流れでバンドル分割できると思います👍
ベースができていれば、機能の塊が増えるときにバンドルを追加するのは容易ですね。
というわけで、参考になれば幸いです。
Discussion