首页 文章

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

提问于
浏览
199

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

问题是,在一个特定的应用程序中,例如,类似于Twitter的应用程序,如果一切都真的在一个通用的包中,比如说_1148204?

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

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

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

EDIT#1

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

6 回答

  • 5

    我写了一篇关于这个主题的更全面和更新的博客文章: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 中的实体保留在任何包之外,我已经更改了 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添加到 config.yml 来告诉JMSDiExtraBundle扫描 src 文件夹中的服务:

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

    然后你define controllers as services并将它们放在 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寻找没有捆绑的模板 . 我通过覆盖模板guesser服务来做到这一点,但由于Symfony 2.0和2.1之间的方法不同,我正在为它们提供版本 .

    覆盖Symfony 2.1模板猜测器

    我已经创建了一个bundle来为你做这件事 .

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

    然后通过将此添加到 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' %}
    
  • 5

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

  • -2

    通常的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 full stack框架默认提供的 .

    您将遇到问题的地方是您何时想要使用翻译文件(如xliff),因为它们是通过包 only 发现的 .

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

  • 11

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

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

  • 20

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

    Iltar van der Berg撰写

    TLDR:

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

    909 Symfony: How to Bundle作者:Toni Uebernickel .

    TLDR:

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

  • 216

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

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

    之后,如果您想使用其他技术来开发POC,您可以轻松翻译它,因为您没有在捆绑构思中构建所有代码 .

    对于所有这些概念,你都不会对此有所了解 . 捆绑很好,但捆绑一切,每天都不好 .

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

相关问题