在Symfony 2.x中,一切都应该是一个捆绑吗?

时间:2012-04-03 18:45:53

标签: symfony architecture bundle

我知道像this这样的问题,人们倾向于讨论一般的Symfony 2概念。

问题是,在特定的应用程序中,例如,类似于Twitter的应用程序,是否所有内容都应该在通用包中,如official docs所说的那样?

我问这个的原因是因为当我们开发应用程序时,一般来说,我们不希望将代码高度耦合到一些全栈胶水框架。

如果我开发一个基于Symfony 2的应用程序,并且在某些时候,我认为Symfony 2并不是保持开发的最佳选择,这对我来说是个问题吗?

所以一般的问题是:为什么一切都是一个好东西?

修改#1

自从我问这个问题差不多一年后,我写了一篇article来分享我对这个主题的了解。

6 个答案:

答案 0 :(得分:217)

我已就此主题撰写了更全面,更新的博文: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命名空间并将其替换为所选框架内容。

请注意,我在此建议的是 app 特定代码。对于可重复使用的捆绑包,我仍建议使用the best practices

保持实体不受捆绑

要将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

保持控制器不受捆绑

首先,您需要告诉JMSDiExtraBundlesrc文件夹添加到config.yml,以便扫描jms_di_extra: locations: directories: %kernel.root_dir%/../src 文件夹:

Controller

然后你define controllers as services并将它们放在<?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 ]; } } 命名空间下:

<?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;
        }
    }
}

请注意,我正在使用ElnurAbstractControllerBundle来简化将控制器定义为服务。

剩下的最后一件事是告诉Symfony寻找没有捆绑的模板。我通过覆盖模板guesser服务来做到这一点,但由于Symfony 2.0和2.1之间的方法不同,我正在为它们提供版本。

覆盖Symfony 2.1+模板guesser

我已经为您创建了bundle

覆盖Symfony 2.0模板侦听器

首先,定义类:

config.yml

然后告诉Symfony将其添加到parameters: jms_di_extra.template_listener.class: Vendor\Listener\TemplateListener

app/Resources/views

使用不带包的模板

现在,您可以使用捆绑包中的模板。将它们保存在app/Resources/views/User/add.html.twig文件夹下。例如,上述示例控制器中这两个操作的模板位于:

  • app/Resources/views/User/profile.html.twig
  • {% include ':Controller:view.html.twig' %}

在引用模板时,只需省略捆绑部分:

{{1}}

答案 1 :(得分:20)

当然,你可以解耦你的申请。只需将其开发为库并将其集成到symfony vendor/ - 文件夹中(使用depscomposer.json,这取决于您使用Symfony2.0还是Symfony2.1)。但是,您至少需要一个捆绑,作为&#34;前端&#34;您的库,Symfony2找到控制器(等等)。

答案 2 :(得分:11)

通常的symfony发行版可以在没有任何额外(应用程序)包的情况下工作,具体取决于您希望在完整堆栈框架中使用多少功能。

例如,您的控制器可以是任何可调用的,只要它们被自动加载就可以放在项目结构的任何位置。

在路由定义文件中,您可以使用:

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

它可以是任何普通的旧php对象,只是因为它必须返回一个Symfony\Component\HttpFoundation\Response对象而绑定到框架。

您的twig模板(或其他)可以像app/Resources/views/template.html.twig一样放置,并且可以使用::template.html.twig逻辑名称进行渲染。

所有DI服务都可以在app / config / config.yml中定义(或者从app/config/services.yml导入,并且所有服务类也可以是任何普通的旧php对象。根本不依赖于框架。

所有这些都是symfony完整堆栈框架默认提供的。

如果您想要使用翻译文件(例如xliff),那么您将遇到问题,因为它们是通过捆绑 发现的。

symfony-light发行版旨在通过发现通常只能通过捆绑包发现的所有内容来解决这类问题。

答案 3 :(得分:5)

您可以使用KnpRadBundle,它会尝试简化项目结构。

另一种方法是使用src/Company/Bundle/FrontendBundle作为bundle,src/Company/Stuff/Class.php用于symfony独立的类,可以在框架之外重用

答案 4 :(得分:5)

由于已经过去了5年,这里有关于Symfony Bundles的更多文章。

    Iltar van der Berg的
  1. What are Bundles in Symfony?
  2. TLDR:

      

    您是否需要直接在应用程序中使用多个捆绑包?最有可能的   不。你最好写一个AppBundle来防止意大利面   依赖。您只需按照best practices即可   工作得很好。

    1. Symfony: How to Bundle来自Toni Uebernickel。
    2. TLDR:

        

      为您的应用程序逻辑仅创建一个名为AppBundle的捆绑包。   一个AppBundle - 但请不要把你的应用程序逻辑放在那里!

答案 5 :(得分:-2)

Symfony框架非常适合快速启动概念验证,所有代码都可以在src /

中的默认包应用程序中输入

在此捆绑包中,您可以根据需要构建代码。

如果您想使用其他技术来开发POC,您可以轻松地将其翻译,因为您不会在捆绑概念中构建所有代码。

对于所有的概念,你都不要把它当作极端的。捆绑很好,但捆绑一切,每天都不好。

也许您可以使用Silex(Symfony微框架)来开发您的概念证明,以减少捆绑第三方的影响。