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

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

问题是,在一个特定的应用程序中,比如,一个类似twitter的应用程序,所有东西都应该在一个通用的包中,比如官方文档 ?

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

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

所以一般的问题是:为什么所有东西捆绑在一起都是好事?

编辑# 1

自从我问这个问题差不多一年了,我写了一个文章来分享我在这个话题上的知识。

31806 次浏览

当然,您可以对应用程序进行解耦。只需将其开发为一个库,并将其集成到symfony __abc0文件夹中(通过使用depscomposer.json,取决于你是使用Symfony2.0还是Symfony2.1)。但是,您至少需要一个捆绑包,作为您的库的“前端”,Symfony2在其中查找控制器(等等)。

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

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

我已经写了一篇关于这个主题的更全面和更新的博客文章: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中的实体保持在任何bundle之外,我改变了config.yml中的doctrine部分

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

然后告诉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

当引用模板时,省略bundle部分:

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

通常的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)时,你会遇到问题,因为它们是通过bundle 只有发现的。

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

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

在这个包中,您可以按照自己的意愿构建代码。

之后,如果你想使用其他技术来开发POC,你可以很容易地翻译它,因为你不需要在包概念中构建所有的代码。

对于所有的概念,你没有极端。捆绑很好,但捆绑一切,每天都不好。

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

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

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

TLDR:

你的应用程序直接需要多个包吗?最有可能的 不是。你最好写一个AppBundle来防止意大利面条 依赖关系。你可以简单地跟随最佳实践,它就会 工作好。< / p >

  1. Symfony:如何捆绑由Toni Uebernickel。

TLDR:

仅为应用程序逻辑创建一个名为AppBundle的bundle。 一个AppBundle -但请不要把你的应用逻辑放在那里!< / p >