在 Symfony 2.x 中,一切真的应该是一个包吗? [英] Should everything really be a bundle in Symfony 2.x?

查看:23
本文介绍了在 Symfony 2.x 中,一切真的应该是一个包吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道像 this,人们倾向于在这里讨论捆绑的一般 Symfony 2 概念.

问题是,在一个特定的应用程序中,例如一个类似 twitter 的应用程序,一切都应该在一个通用包中,比如 官方文档 怎么说?

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

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

所以一般的问题是:为什么一切都是捆绑的好事?

编辑#1

自从我问这个问题快一年了,我写了一个 文章 分享我关于这个主题的知识.

解决方案

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

学说:# ...形式:# ...自动映射:真

学说:# ...形式:# ...映射:模型:类型:注释目录:%kernel.root_dir%/../src/Vendor/Model前缀:供应商\型号别名:型号is_bundle: 假

实体的名称——从 Doctrine 存储库访问——在这种情况下以 Model 开头,例如,Model:User.

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

让控制器远离捆绑

首先,您需要告诉 JMSDiExtraBundle 扫描 src 文件夹中的通过将其添加到 config.yml 来提供服务:

jms_di_extra:地点:目录:%kernel.root_dir%/../src

然后你将控制器定义为服务并将它们放在Controller 命名空间:

<?php命名空间供应商\控制器;使用 Symfony\Component\HttpFoundation\Request;使用 Symfony\Component\HttpFoundation\RedirectResponse;使用 Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;使用 Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;使用 JMS\DiExtraBundle\Annotation\Service;使用 JMS\DiExtraBundle\Annotation\InjectParams;使用 JMS\SecurityExtraBundle\Annotation\Secure;使用 Elnur\AbstractControllerBundle\AbstractController;使用供应商\服务\用户服务;使用供应商\型号\用户;/*** @Service("user_controller", parent="elnur.controller.abstract")* @Route(service="user_controller")*/类 UserController 扩展了 AbstractController{/*** @var 用户服务*/私人 $userService;/*** @InjectParams** @param UserService $userService*/公共函数 __construct(UserService $userService){$this->userService = $userService;}/*** @Route("/user/add", name="user.add")* @模板* @Secure("ROLE_ADMIN")** @param 请求 $request* @return 数组*/公共函数 addAction(Request $request){$user = 新用户;$form = $this->formFactory->create('user', $user);if ($request->getMethod() == 'POST') {$form->bind($request);如果 ($form->isValid()) {$this->userService->save($user);$request->getSession()->getFlashBag()->add('success', 'user.add.success');return new RedirectResponse($this->router->generate('user.list'));}}返回 ['form' =>$form->createView()];}/*** @Route("/user/profile", name="user.profile")* @模板* @Secure("ROLE_USER")** @param 请求 $request* @return 数组*/公共功能 profileAction(Request $request){$user = $this->getCurrentUser();$form = $this->formFactory->create('user_profile', $user);if ($request->getMethod() == 'POST') {$form->bind($request);如果 ($form->isValid()) {$this->userService->save($user);$request->getSession()->getFlashBag()->add('success', 'user.profile.edit.success');return new RedirectResponse($this->router->generate('user.view', ['用户名' =>$user->getUsername()]));}}返回 ['形式' =>$form->createView(),'用户' =>$用户];}}

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

剩下的最后一件事是告诉 Symfony 寻找没有包的模板.我通过覆盖模板猜测器服务来做到这一点,但由于 Symfony 2.0 和 2.1 的方法不同,我为它们提供了版本.

覆盖 Symfony 2.1+ 模板猜测器

我已经创建了一个 bundle 来帮您做到这一点.

覆盖 Symfony 2.0 模板监听器

首先定义类:

<?php命名空间供应商\监听器;使用 InvalidArgumentException;使用 Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;使用 Symfony\Component\HttpFoundation\Request;使用 Symfony\Component\HttpKernel\Bundle\Bundle;使用 Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener 作为 FrameworkExtraTemplateListener;使用 JMS\DiExtraBundle\Annotation\Service;类 TemplateListener 扩展了 FrameworkExtraTemplateListener{/*** @param 数组 $controller* @param 请求 $request* @param 字符串 $engine* @throws InvalidArgumentException* @return 模板参考*/公共函数 guessTemplateName($controller, Request $request, $engine = 'twig'){if (!preg_match('/Controller\\\(.+)Controller$/', get_class($controller[0]), $matchController)) {throw new InvalidArgumentException(sprintf('%s"类看起来不像控制器类(它必须在Controller"子命名空间中,并且类名必须以Controller"结尾)', get_class($控制器[0])));}if (!preg_match('/^(.+)Action$/', $controller[1], $matchAction)) {throw new InvalidArgumentException(sprintf('%s"方法看起来不像一个动作方法(它不以动作结尾)', $controller[1]));}$bundle = $this->getBundleForClass(get_class($controller[0]));返回新的模板参考($捆绑?$bundle->getName() : null,$matchController[1],$matchAction[1],$request->getRequestFormat(),$引擎);}/*** @param 字符串 $class* @return 捆绑包*/受保护的函数 getBundleForClass($class){尝试 {return parent::getBundleForClass($class);} catch (InvalidArgumentException $e) {返回空;}}}

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

参数: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' %}

I'm aware of questions like this, where people tend to discuss the general Symfony 2 concept of bundle.

The thing is, in a specific application, like, for instance, a twitter-like application, should everything really be inside a generic bundle, like the official docs say?

The reason I'm asking this is because when we develop applications, in general, we don't want to highly couple our code to some full-stack glue framework.

If I develop a Symfony 2 based application and, at some point, I decide Symfony 2 is not really the best choice to keep the development going, will that be a problem for me?

So the general question is: why is everything being a bundle a good thing?

EDIT#1

Almost a year now since I asked this question I wrote an article to share my knowledge on this topic.

解决方案

I've written a more thorough and updated blog post on this topic: http://elnur.pro/symfony-without-bundles/


No, not everything has to be in a bundle. You could have a structure like this:

  • src/Vendor/Model — for models,
  • src/Vendor/Controller — for controllers,
  • src/Vendor/Service — for services,
  • src/Vendor/Bundle — for bundles, like src/Vendor/Bundle/AppBundle,
  • etc.

This way, you would put in the AppBundle only that stuff that is really Symfony2 specific. If you decide to switch to another framework later, you would get rid of the Bundle namespace and replace it with the chosen framework stuff.

Please note that what I'm suggesting here is for app specific code. For reusable bundles, I still suggest using the best practices.

Keeping entities out of bundles

To keep entities in src/Vendor/Model outside of any bundle, I've changed the doctrine section in config.yml from

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

to

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

Entities's names — to access from Doctrine repositories — begin with Model in this case, for example, Model:User.

You can use subnamespaces to group related entities together, for example, src/Vendor/User/Group.php. In this case, the entity's name is Model:User\Group.

Keeping controllers out of bundles

First, you need to tell JMSDiExtraBundle to scan the src folder for services by adding this to config.yml:

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

Then you define controllers as services and put them under the Controller namespace:

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

Note that I'm using my ElnurAbstractControllerBundle to simplify defining controllers as services.

The last thing left is to tell Symfony to look for templates without bundles. I do this by overriding the template guesser service, but since the approach is different between Symfony 2.0 and 2.1, I'm providing versions for both of them.

Overriding the Symfony 2.1+ template guesser

I've created a bundle that does that for you.

Overriding the Symfony 2.0 template listener

First, define the class:

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

And then tell Symfony to use it by adding this to config.yml:

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

Using templates without bundles

Now, you can use templates out of bundles. Keep them under the app/Resources/views folder. For example, templates for those two actions from the example controller above are located in:

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

When referring to a template, just omit the bundle part:

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

这篇关于在 Symfony 2.x 中,一切真的应该是一个包吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆