Symfony 2.xではすべてを本当にバンドルにする必要がありますか?


205

Symfony 2のバンドルの一般的な概念について人々が議論する傾向があるこのような質問を私は知っています。

事は、例えばツイッターのようなアプリケーションのような特定のアプリケーションでは、公式のドキュメントが言うように、すべてが本当に一般的なバンドルの中にあるべきですか?

私がこれを尋ねる理由は、一般に、アプリケーションを開発するとき、コードをフルスタックの接着剤フレームワークに高度に結合したくないためです。

Symfony 2ベースのアプリケーションを開発していて、ある時点で、Symfony 2が開発続けるのに本当に最良の選択ではないと判断した場合、それは私にとって問題でしょうか?

したがって、一般的な質問は、なぜすべてがバンドルであることが良いことなのかということです。

編集#1

この質問をしてからほぼ1年が経ち、このトピックに関する私の知識を共有するための記事を書きました。


1
これは単なるコメントであり、回答ではありません。個人的には、プロジェクトを始める前にフレームワークを注意深く選ぶべきだと思います。すべてのフレームワークには独自の方法があります。そのため、フレームワークはその方法を最適にサポートするツールを提供します。私たちがそのように好きなら、私たちは従います。他にも選択肢があります。私たちは鋸の代わりにナイフを使って木を切りたくありません。しかし、それはあなたが提起した非常に興味深い質問です:)
Anh Nguyen

回答:


219

私はこのトピックについてより完全で更新されたブログ投稿を書きました:http : //elnur.pro/symfony-without-bundles/


いいえ、すべてがバンドルされている必要はありません。次のような構造にすることができます。

  • src/Vendor/Model —モデルの場合、
  • src/Vendor/Controller —コントローラの場合、
  • src/Vendor/Service —サービスについては、
  • src/Vendor/Bundle—などのバンドルのsrc/Vendor/Bundle/AppBundle場合

このようにして、AppBundle本当にSymfony2固有のものだけを配置します。後で別のフレームワークに切り替えることにした場合は、Bundle名前空間を削除して、選択したフレームワークのものに置き換えます。

ここで提案するのはアプリ固有のコード用であることに注意してください。再利用可能なバンドルについては、引き続きベストプラクティスを使用することをお勧めします。

エンティティをバンドルから除外する

エンティティをsrc/Vendor/Modelバンドル外に保持するために、doctrineセクションをconfig.ymlから変更しました

doctrine:
    # ...
    orm:
        # ...
        auto_mapping: true

doctrine:
    # ...
    orm:
        # ...
        mappings:
            model:
                type: annotation
                dir: %kernel.root_dir%/../src/Vendor/Model
                prefix: Vendor\Model
                alias: Model
                is_bundle: false

エンティティの名前— DoctrineリポジトリからアクセスするにModelは— この場合、例えばで始まりますModel:User

サブネームスペースを使用して、関連するエンティティをグループ化できます(例:)src/Vendor/User/Group.php。この場合、エンティティの名前はModel:User\Groupです。

コントローラーをバンドルから除外する

まず、JMSDiExtraBundleに次のようにsrc追加して、サービスのフォルダーをスキャンするように指示する必要がありますconfig.yml

jms_di_extra:
    locations:
        directories: %kernel.root_dir%/../src

次に、コントローラーをサービスとして定義し、Controller名前空間の下に置きます。

<?php
namespace Vendor\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\InjectParams;
use JMS\SecurityExtraBundle\Annotation\Secure;
use Elnur\AbstractControllerBundle\AbstractController;
use Vendor\Service\UserService;
use Vendor\Model\User;

/**
 * @Service("user_controller", parent="elnur.controller.abstract")
 * @Route(service="user_controller")
 */
class UserController extends AbstractController
{
    /**
     * @var UserService
     */
    private $userService;

    /**
     * @InjectParams
     *
     * @param UserService $userService
     */
    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    /**
     * @Route("/user/add", name="user.add")
     * @Template
     * @Secure("ROLE_ADMIN")
     *
     * @param Request $request
     * @return array
     */
    public function addAction(Request $request)
    {
        $user = new User;
        $form = $this->formFactory->create('user', $user);

        if ($request->getMethod() == 'POST') {
            $form->bind($request);

            if ($form->isValid()) {
                $this->userService->save($user);
                $request->getSession()->getFlashBag()->add('success', 'user.add.success');

                return new RedirectResponse($this->router->generate('user.list'));
            }
        }

        return ['form' => $form->createView()];
    }

    /**
     * @Route("/user/profile", name="user.profile")
     * @Template
     * @Secure("ROLE_USER")
     *
     * @param Request $request
     * @return array
     */
    public function profileAction(Request $request)
    {
        $user = $this->getCurrentUser();
        $form = $this->formFactory->create('user_profile', $user);

        if ($request->getMethod() == 'POST') {
            $form->bind($request);

            if ($form->isValid()) {
                $this->userService->save($user);
                $request->getSession()->getFlashBag()->add('success', 'user.profile.edit.success');

                return new RedirectResponse($this->router->generate('user.view', [
                    'username' => $user->getUsername()
                ]));
            }
        }

        return [
            'form' => $form->createView(),
            'user' => $user
        ];
    }
}

ElnurAbstractControllerBundleを使用して、コントローラーをサービスとして簡単に定義できることに注意してください。

最後に、Symfonyにバンドルのないテンプレートを探すように指示します。これはテンプレート推測サービスをオーバーライドすることで行いますが、Symfony 2.0と2.1ではアプローチが異なるため、両方のバージョンを提供しています。

Symfony 2.1以降のテンプレート推測機能をオーバーライドする

これを行うバンドルを作成しました。

Symfony 2.0テンプレートリスナーをオーバーライドする

まず、クラスを定義します。

<?php
namespace Vendor\Listener;

use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener as FrameworkExtraTemplateListener;
use JMS\DiExtraBundle\Annotation\Service;

class TemplateListener extends FrameworkExtraTemplateListener
{
    /**
     * @param array   $controller
     * @param Request $request
     * @param string  $engine
     * @throws InvalidArgumentException
     * @return TemplateReference
     */
    public function guessTemplateName($controller, Request $request, $engine = 'twig')
    {
        if (!preg_match('/Controller\\\(.+)Controller$/', get_class($controller[0]), $matchController)) {
            throw new InvalidArgumentException(sprintf('The "%s" class does not look like a controller class (it must be in a "Controller" sub-namespace and the class name must end with "Controller")', get_class($controller[0])));

        }

        if (!preg_match('/^(.+)Action$/', $controller[1], $matchAction)) {
            throw new InvalidArgumentException(sprintf('The "%s" method does not look like an action method (it does not end with Action)', $controller[1]));
        }

        $bundle = $this->getBundleForClass(get_class($controller[0]));

        return new TemplateReference(
            $bundle ? $bundle->getName() : null,
            $matchController[1],
            $matchAction[1],
            $request->getRequestFormat(),
            $engine
        );
    }

    /**
     * @param string $class
     * @return Bundle
     */
    protected function getBundleForClass($class)
    {
        try {
            return parent::getBundleForClass($class);
        } catch (InvalidArgumentException $e) {
            return null;
        }
    }
}

そして、これをconfig.yml以下に追加することでそれを使用するようにSymfonyに伝えます:

parameters:
    jms_di_extra.template_listener.class: Vendor\Listener\TemplateListener

バンドルなしのテンプレートの使用

これで、バンドルからテンプレートを使用できます。app/Resources/viewsフォルダの下に保管してください。たとえば、上記のコントローラーの例の2つのアクションのテンプレートは、次の場所にあります。

  • app/Resources/views/User/add.html.twig
  • app/Resources/views/User/profile.html.twig

テンプレートを参照するときは、バンドル部分を省略します。

{% include ':Controller:view.html.twig' %}

2
それは実際には非常に興味深いアプローチです。これにより、アプリケーションをフレームワーク自体にほとんど結合することなく、コミュニティーが使用できる特定の機能セットを含む実際のバンドルを開発することもできます。
ダニエル・リベイロ

57
コミュニティと共有するコードをSymfony2にも結合しないようにするには、一般的なものをライブラリに入れ、そのライブラリをSymfony2と統合するバンドルを作成します。
Elnur Abdurrakhimov 2012

9
コード生成コマンドに依存しない限り、これは興味深いアイデアです。generate:doctrine:crudたとえば、エンティティ(elnurの場合はモデル)が機能するためにバンドル内にあることを期待しています。
geca

2
このアプローチでは、CLIアプリ/コンソールインターフェイスの機能を回復する方法はありますか?モデルをバンドル外の場所に置くというアイデアは気に入っていますが、CLI機能へのアクセスを保持したいです。
アンディベアード

3
これはバンドルに入れる必要があります:)
d0001

20

もちろん、アプリケーションを分離することもできます。それをライブラリとして開発し、symfony vendor/-folderに統合します(depsまたはcomposer.jsonを使用して、Symfony2.0またはSymfony2.1を使用するかどうかに依存します)。ただし、ライブラリの「フロントエンド」として機能する少なくとも1つのバンドルが必要です。Symfony2がコントローラーなどを見つけます。


2
タグのために、symfony-2.0現在の2.0バージョンを使用すると想定します。この場合、好きなところにgitリポジトリを作成し、symfonyから独立して開発したいものをすべて入れます。symfony-projectを更新し、symfony.com / doc / current / cookbook / workflow /…のdepsように-fileを更新します。次に、symfony固有のもの用に1つ(または複数)のアプリケーションバンドル()を作成します。php app/console generate:bundle
KingCrunch

11

通常のsymfonyディストリビューションは、完全なスタックフレームワークから使用する機能の量に応じて、追加の(アプリケーション)バンドルがなくても機能します。

たとえば、コントローラーは、オートロードされるとすぐに、プロジェクト構造の任意の場所に配置できる呼び出し可能オブジェクトにすることができます。

ルーティング定義ファイルでは、以下を使用できます。

test:
    pattern:   /test
    defaults:  { _controller: Controller\Test::test }

それは、Symfony\Component\HttpFoundation\Responseオブジェクトを返す必要があるという事実によってのみフレームワークに関連付けられている、プレーンな古いphpオブジェクトにすることができます。

小枝テンプレート(またはその他)はapp/Resources/views/template.html.twig::template.html.twig論理名を使用して同様に配置およびレンダリングできます。

すべてのDIサービスはapp / config / config.ymlで定義できます(またはapp/config/services.yml、たとえばからインポートできます。すべてのサービスクラスは、フレームワークにまったく関連付けられていない、プレーンな古いphpオブジェクトでもかまいません。

これらすべてはsymfonyのフルスタックフレームワークによってデフォルトで提供されています。

問題が発生するのは、翻訳ファイル(xliffなど)を使用する場合です。これらはバンドルでのみ検出されるためです。

symfonyの光のみ通常のバンドルで発見されるだろうすべてのものを発見することにより、問題のこれらの種類を解決するための配布を目指しています。


5

プロジェクト構造を単純化しようとするKnpRadBundleを使用できます。

別のアプローチはsrc/Company/Bundle/FrontendBundle、たとえば、src/Company/Stuff/Class.phpsymfonyに依存せず、フレームワークの外で再利用できるバンドルやクラスを使用することです


しかし、それから私はアプリケーションをKnpRadBundleに結合します...この問題についてもっと簡単なアプローチはありませんか?
ダニエル・リベイロ

1
symfonyに依存している部分(コントローラー、モデル、テンプレートなど)は、symfonyを使用しているため(クラスの拡張、ヘルパーの使用など)、常にsymfonyに結合されます。単独で機能するクラスはCompany名前空間にあり、依存関係コンテナーを使用してそれらをロードできます。これらのクラスは、フレームワークに依存しません。
miguel_ibero

1
問題は、その概念Bundleが直接共有することです。アプリケーションを作成するとき、コミュニティ主導のモジュールとして意図的に構築した部分を除いて、コードを共有したくありません。私が間違っている?
ダニエルリベイロ

バンドルを共有する必要はありません。バンドルを、いくつかの構成を持つクラスのグループと考えてください。各プロジェクトでは、異なるバンドルを使用できます。
miguel_ibero


5

もう5年経っているので、symfonyバンドルに関する記事は他にもあります。

  1. symfonyのバンドルとは何ですか?Iltar van der Bergによる。

TLDR:

アプリケーションに複数のバンドルが直接必要ですか?おそらくそうではない。依存関係のスパゲッティを防ぐために、AppBundleを作成する方がよいでしょう。ベストプラクティスに従うだけで問題なく機能します。

  1. Symfony: Toni Uebernickelによるバンドル方法

TLDR:

アプリケーションロジック用にAppBundleと呼ばれるバンドルを1つだけ作成します。1つのAppBundle-ただし、アプリケーションロジックをそこに配置しないでください。


-2

symfonyフレームワークは概念実証をすばやく起動するのに非常に優れており、すべてのコードはsrc /のデフォルトのバンドルアプリケーション内に入力できます

このバンドルでは、必要に応じてコードを構造化できます。

POCを開発するために他のテクノロジーを使用したい場合は、バンドルの概念ですべてのコードを構造化しないため、簡単にそれを変換できます。

すべての概念について、これを過激化していません。バンドルは良いですが、すべてをバンドルし、毎日は良くありません。

おそらく、Silex(Symfonyマイクロフレームワーク)を使用して、バンドルのサードパーティの影響を減らすための概念実証を開発できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.