真的应该将所有东西捆绑在Symfony 2.x中吗?


205

我知道类似这样的问题,人们倾向于讨论笼统的Symfony 2概念。

问题是,在特定的应用程序(例如类似Twitter的应用程序)中,所有内容都应该像官方文档中所说的那样真正包含在通用包中吗?

我之所以这样问,是因为通常在开发应用程序时,我们不想将代码高度耦合到某些全栈胶合框架。

如果我开发基于Symfony 2的应用程序,并且在某个时候决定将Symfony 2并不是继续开发的最佳选择,那么这对我来说会是一个问题吗?

因此,普遍的问题是:为什么一切都捆绑在一起是一件好事?

编辑#1

自从我问了这个问题以来,已经快一年了,我写了一篇文章来分享我对该主题的知识。


1
这只是评论,不是答案。我个人认为,在开始项目之前,我们应该仔细选择框架。每个框架都有自己的做事方式,因此它将提供工具来最好地支持这种方式。如果我们喜欢这种方式,我们就会遵循。还有其他选择。我们不想用刀代替锯来切割木头。但这是您提出的一个非常有趣的问题:)
Anh Nguyen 2014年

Answers:


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

Model在这种情况下,实体名称(可从Doctrine存储库访问)以开头Model:User

您可以使用子命名空间将相关实体分组在一起,例如src/Vendor/User/Group.php。在这种情况下,实体的名称为Model:User\Group

使控制器脱离捆绑

首先,您需要告诉JMSDiExtraBundlesrc通过将其添加到来扫描文件夹中的服务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;
        }
    }
}

然后通过将其添加到Symfony来使用它config.yml

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' %}

2
这实际上是一种非常有趣的方法。这样,我还可以开发真正的捆绑包,其中包含社区可以使用的特定功能集,而几乎不会将我的应用程序耦合到框架本身。
丹尼尔·里贝罗

57
为了使您与社区共享的代码也没有与Symfony2耦合,您可以将常规内容放入一个库中,然后创建一个将该库与Symfony2集成在一起的捆绑软件。
Elnur Abdurrakhimov

9
只要您不依赖任何代码生成命令,这就是一个有趣的想法。generate:doctrine:crud例如,期望实体(在elnur的情况下= model)位于捆绑包中才能正常工作。
geca

2
使用这种方法,是否有任何方法可以重新获得CLI应用程序/控制台界面的功能?我喜欢将模型放在任何捆绑软件之外的地方的想法,但是我想保留对CLI功能的访问。
安迪·贝尔德

3
这应该放在捆绑中:)
d0001 2012年

20

当然,您可以取消应用程序的耦合。只需将其开发为一个库并将其集成到symfony vendor/-folder(通过使用depsor或composer.json,取决于是否使用Symfony2.0或Symfony2.1)。但是,您至少需要一个捆绑包,作为您图书馆的“前端”,Symfony2在其中找到控制器(等等)。


2
由于有了标签,symfony-2.0我假设您使用的是当前的2.0版本。在这种情况下,您可以在任何需要的地方创建一个git存储库,并将所有内容放入其中,而无需依赖symfony进行开发。在您的symfony-project中,更新您的deps-file,如此处提到的symfony.com/doc/current/cookbook/workflow / ...然后,只需php app/console generate:bundle为symfony专用的东西创建一个(或多个)application-bundle(s)。
KingCrunch'4

11

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

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

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

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

它可以是任何普通的旧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全栈框架提供的。

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

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


5

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

另一种方法是src/Company/Bundle/FrontendBundle,例如,将束和src/Company/Stuff/Class.php类用于独立于symfony并可以在框架外部重用的类


但是,然后我会将应用程序耦合到KnpRadBundle ...在此问题上没有更简单的方法吗?
丹尼尔·里贝罗

1
依赖symfony的部分(控制器,模型,模板等)将始终与symfony耦合,因为您正在使用它(扩展类,使用助手等)。单独工作的类将在Company名称空间中,您可以使用依赖项容器加载它们。这些类可以是独立于框架的。
miguel_ibero 2012年

1
问题是,Bundle公开共享的概念直接存在。当我编写一些应用程序时,除了我有意将其构建为社区驱动的模块之外,我不想共享我的代码。我错了吗?
丹尼尔·里贝罗

您不必共享捆绑包。可以将捆绑软件看作是具有某些配置的一组类。在每个项目中,您可以有不同的捆绑包。
miguel_ibero 2012年

您应该阅读symfony书
miguel_ibero 2012年

5

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

  1. Symfony中的捆绑包是什么?由Iltar van der Berg撰写。

TLDR:

您是否直接在应用程序中需要多个捆绑软件?很有可能不会。最好编写一个AppBundle来防止产生意粉。您只需遵循最佳做法,它就可以正常工作。

  1. Symfony:如何捆绑 Toni Uebernickel。

TLDR:

仅为您的应用程序逻辑创建一个名为AppBundle的捆绑包。一个AppBundle-但是请不要在其中放置应用程序逻辑!


-2

Symfony框架非常适合快速启动概念验证,并且所有代码都可以在src /中的默认捆绑应用程序中输入

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

之后,如果您想使用其他技术来开发POC,则可以轻松地将其转换,因为您无需将所有代码都按照捆绑包的概念进行结构化。

对于所有概念,您都不会感到极端。捆绑是好的,但是捆绑所有东西,每天都不好。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.