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

Dan*_*iro 203 architecture bundle symfony

我所知道的这样的问题这样,人们往往会讨论束的大致的Symfony 2的概念.

问题是,在特定的应用程序中,例如,类似于Twitter的应用程序,如果一切都真的在一个通用的包中,就像官方文档说的那样?

我之所以这么说是因为当我们开发应用程序时,一般来说,我们不希望将代码高度耦合到一些全栈胶水框架.

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

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

编辑#1

自从我提出这个问题以来差不多一年了,我写了一篇文章来分享我对这个主题的了解.

Eln*_*mov 218

我写了一篇关于这个主题的更全面和更新的博客文章: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
Run Code Online (Sandbox Code Playgroud)

doctrine:
    # ...
    orm:
        # ...
        mappings:
            model:
                type: annotation
                dir: %kernel.root_dir%/../src/Vendor/Model
                prefix: Vendor\Model
                alias: Model
                is_bundle: false
Run Code Online (Sandbox Code Playgroud)

实体的名称 - 从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
Run Code Online (Sandbox Code Playgroud)

然后将控制器定义为服务并将它们放在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
        ];
    }
}
Run Code Online (Sandbox Code Playgroud)

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

剩下的最后一件事是告诉Symfony寻找没有捆绑的模板.我通过覆盖模板guesser服务来做到这一点,但由于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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后通过将此添加到config.yml以下内容告诉Symfony使用它:

parameters:
    jms_di_extra.template_listener.class: Vendor\Listener\TemplateListener
Run Code Online (Sandbox Code Playgroud)

使用没有捆绑的模板

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

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

引用模板时,只需省略包部分:

{% include ':Controller:view.html.twig' %}
Run Code Online (Sandbox Code Playgroud)

  • 为了使您与社区共享的代码也不与Symfony2耦合,您可以将常规内容放入库中,然后创建一个将该库与Symfony2集成的包. (57认同)
  • 只要您不依赖任何代码生成命令,这是一个有趣的想法.例如,`generate:doctrine:crud`期望实体(= elnur的情况下的模型)在一个包内以便工作. (9认同)
  • 这应该放在一个包:) (3认同)
  • 这实际上是一种非常有趣的方法.有了这个,我还可以开发真正的包,其中包含社区可以使用的特定功能集,而不会将我的应用程序很难耦合到框架本身. (2认同)
  • 有了这种方法,有没有办法重新获得CLI应用/控制台界面的功能?我喜欢将模型保留在任何捆绑包之外的位置,但我希望保留对CLI功能的访问权限. (2认同)

Kin*_*nch 20

当然,您可以解耦您的应用程序.只需将其开发为库并将其集成到symfony vendor/-folder中(使用deps或者composer.json,使用Symfony2.0或Symfony2.1).但是,您至少需要一个捆绑包,它充当您的库的"前端",Symfony2在其中找到控制器(等等).

  • 由于标签`symfony-2.0`,我假设你使用当前的2.0版本.在这种情况下,您可以随意创建一个git存储库并将所有内容放入其中,您希望独立于symfony进行开发.在你的symfony项目更新你的`deps`文件,如这里提到的http://symfony.com/doc/current/cookbook/workflow/new_project_git.html#cookbook-managing-vendor-libraries然后只创建一个(或更多) application-bundle(s)(`php app/console generate:bundle`)用于特定于symfony的东西. (2认同)

Flo*_*ian 11

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

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

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

test:
    pattern:   /test
    defaults:  { _controller: Controller\Test::test }
Run Code Online (Sandbox Code Playgroud)

它可以是任何普通的旧php对象,只能通过它必须返回一个Symfony\Component\HttpFoundation\Response对象来绑定到框架.

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

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

所有这些都是symfony full stack框架默认提供的.

在这里你将有一个问题是,当你将要使用的翻译文件(如XLIFF),因为它们是通过捆绑发现.

symfony的光分布的目的是通过发现,只能通过捆绑来常会发现一切都解决这些类型的问题.


小智 5

您可以使用KnpRadBundle,它试图简化项目结构.

另一种方法是使用src/Company/Bundle/FrontendBundle例如bundle和src/Company/Stuff/Class.phpsymfony独立的类,可以在框架之外重用


Res*_*lov 5

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

  1. 什么是Symfony的捆绑包?作者:Iltar van der Berg.

TLDR:

您是否需要直接在应用程序中使用多个捆绑包?很可能不是.你最好写一个AppBundle来防止依赖的意大利面.您可以简单地遵循最佳实践,它将正常工作.

  1. Symfony:如何捆绑 Toni Uebernickel.

TLDR:

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