Tout devrait-il vraiment être un bundle dans Symfony 2.x?

205

Je suis au courant de questions comme celle-ci , où les gens ont tendance à discuter du concept général de bundle Symfony 2.

Le problème est que, dans une application spécifique, comme, par exemple, une application de type Twitter, tout devrait-il vraiment être à l'intérieur d'un bundle générique, comme le disent les documents officiels ?

La raison pour laquelle je pose cette question est que, lorsque nous développons des applications, en général, nous ne voulons pas coupler fortement notre code à un cadre de collage à pile complète.

Si je développe une application basée sur Symfony 2 et, à un moment donné, je décide que Symfony 2 n'est pas vraiment le meilleur choix pour continuer le développement , est-ce que ce sera un problème pour moi?

La question générale est donc: pourquoi tout ce qui est groupé est-il une bonne chose?

EDIT # 1

Presque un an maintenant, depuis que j'ai posé cette question, j'ai écrit un article pour partager mes connaissances sur ce sujet.

Daniel Ribeiro
la source
1
Ce n'est qu'un commentaire, pas une réponse. Je pense personnellement que nous devons choisir le cadre avec soin avant de commencer le projet. Chaque framework a sa propre façon de faire les choses, il fournira donc des outils pour prendre en charge de cette façon le meilleur. Si nous aimons de cette façon, nous suivons. Il existe d'autres choix. Nous ne voulons pas utiliser un couteau pour couper le bois au lieu d'une scie. Mais c'est une question très intéressante que vous avez posée :)
Anh Nguyen

Réponses:

219

J'ai écrit un article de blog plus approfondi et mis à jour sur ce sujet: http://elnur.pro/symfony-without-bundles/


Non, tout ne doit pas nécessairement être regroupé. Vous pourriez avoir une structure comme celle-ci:

  • src/Vendor/Model - pour les modèles,
  • src/Vendor/Controller - pour les contrôleurs,
  • src/Vendor/Service - pour les services,
  • src/Vendor/Bundle- pour les paquets, comme src/Vendor/Bundle/AppBundle,
  • etc.

De cette façon, vous AppBundlemettriez le seul truc qui est vraiment spécifique à Symfony2. Si vous décidez de passer à un autre framework plus tard, vous vous débarrasserez de l' Bundleespace de noms et le remplacerez par le framework choisi.

Veuillez noter que ce que je suggère ici concerne le code spécifique à l' application . Pour les bundles réutilisables, je suggère toujours d'utiliser les meilleures pratiques .

Garder les entités hors des bundles

Pour conserver les entités en src/Vendor/Modeldehors de tout ensemble, j'ai changé la doctrinesection config.ymldepuis

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

Les noms des entités - pour accéder à partir des référentiels Doctrine - commencent Modeldans ce cas, par exemple,Model:User ,.

Vous pouvez utiliser subnamespaces à des entités liées au groupe ainsi que , par exemple, src/Vendor/User/Group.php. Dans ce cas, le nom de l'entité est Model:User\Group.

Garder les contrôleurs hors des bundles

Tout d'abord, vous devez dire à JMSDiExtraBundle d'analyser le srcdossier pour les services en l'ajoutant à config.yml:

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

Ensuite, vous définissez les contrôleurs comme des services et les placez sous l' Controllerespace de noms:

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

Notez que j'utilise mon ElnurAbstractControllerBundle pour simplifier la définition des contrôleurs en tant que services.

La dernière chose qui reste est de dire à Symfony de rechercher des modèles sans bundles. Je le fais en remplaçant le service de devinette de modèle, mais comme l'approche est différente entre Symfony 2.0 et 2.1, je fournis des versions pour les deux.

Remplacer le devineur de modèles Symfony 2.1+

J'ai créé un bundle qui fait ça pour vous.

Substitution de l'écouteur de modèles Symfony 2.0

Définissez d'abord la classe:

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

Et puis dites à Symfony de l'utiliser en l'ajoutant à config.yml:

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

Utilisation de modèles sans bundles

Maintenant, vous pouvez utiliser des modèles hors des bundles. Gardez-les sous le app/Resources/viewsdossier. Par exemple, les modèles de ces deux actions de l'exemple de contrôleur ci-dessus se trouvent dans:

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

Lorsque vous faites référence à un modèle, omettez simplement la partie de l'ensemble:

{% include ':Controller:view.html.twig' %}
Elnur Abdurrakhimov
la source
2
C'est en fait une approche vraiment intéressante. Avec cela, je peux également développer de vrais bundles qui contiennent un ensemble spécifique de fonctionnalités que la communauté peut utiliser, sans coupler à peine mon application au framework lui-même.
Daniel Ribeiro
57
Pour que le code que vous partagez avec la communauté ne soit pas également couplé à Symfony2, vous pouvez mettre les éléments généraux dans une bibliothèque, puis créer un bundle qui intègre cette bibliothèque avec Symfony2.
Elnur Abdurrakhimov
9
C'est une idée intéressante tant que vous ne comptez sur aucune des commandes de génération de code. generate:doctrine:crudpar exemple s'attend à ce que l'entité (= modèle dans le cas d'elnur) soit à l'intérieur d'un bundle pour fonctionner.
geca
2
Avec cette approche, existe-t-il un moyen de retrouver les fonctionnalités de l'interface de l'application / console CLI? J'aime l'idée de garder mes modèles dans un endroit en dehors de tout ensemble, mais je voudrais conserver l'accès à la fonctionnalité CLI.
Andy Baird
3
Cela devrait être mis dans un bundle :)
d0001
20

Bien sûr, vous pouvez découpler votre application. Développez-le simplement en tant que bibliothèque et vendor/intégrez-le dans le dossier symfony (en utilisant le depsou composer.json, selon que vous utilisez Symfony2.0 ou Symfony2.1). Cependant, vous avez besoin d'au moins un bundle, qui agit comme le "frontend" de votre bibliothèque, où Symfony2 trouve le contrôleur (et autres).

KingCrunch
la source
2
En raison de la balise, symfony-2.0je suppose que vous utilisez la version 2.0 actuelle. Dans ce cas, créez un référentiel git où vous voulez et mettez-y tout, ce que vous voulez développer indépendamment de symfony. Dans votre projet symfony, mettez à jour votre deps-fichier comme mentionné ici symfony.com/doc/current/cookbook/workflow/… Ensuite, créez simplement un (ou plusieurs) paquet (s) d'application ( php app/console generate:bundle) pour les éléments spécifiques à symfony.
KingCrunch
11

Une distribution symfony habituelle peut fonctionner sans bundle (d'application) supplémentaire, selon le nombre de fonctionnalités que vous souhaitez utiliser à partir du framework de pile complet.

Par exemple, vos contrôleurs peuvent être n'importe quel appelable qui peut être placé n'importe où dans la structure de votre projet, dès qu'ils sont chargés automatiquement.

Dans un fichier de définition de routage, vous pouvez utiliser:

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

Il peut s'agir de n'importe quel objet php ancien, uniquement lié au framework par le fait qu'il doit renvoyer un Symfony\Component\HttpFoundation\Responseobjet.

Vos modèles de brindilles (ou autres) peuvent être mis comme app/Resources/views/template.html.twiget peuvent être rendus en utilisant le ::template.html.twignom logique.

Tous les services DI peuvent être définis dans app / config / config.yml (ou importés app/config/services.ymlpar exemple, et toutes les classes de service peuvent également être de vieux objets PHP simples. Pas du tout liés au framework.

Tout cela est fourni par défaut par le framework symfony full stack.

Où vous aurez des problèmes est quand vous voulez utiliser des fichiers de traduction (comme xliff), parce qu'ils sont découverts par des faisceaux seulement .

La distribution symfony-light vise à résoudre ce genre de problèmes en découvrant tout ce qui ne serait généralement découvert qu'à travers des bundles.

Florian Klein
la source
5

Vous pouvez utiliser KnpRadBundle , qui essaie de simplifier la structure du projet.

Une autre approche consiste à utiliser src/Company/Bundle/FrontendBundlepar exemple pour les bundles et src/Company/Stuff/Class.phppour les classes qui sont indépendantes de symfony et qui pourraient être réutilisées en dehors du framework

miguel_ibero
la source
Mais alors je couplerais l'application au KnpRadBundle ... N'y a-t-il pas une approche plus simple à ce sujet?
Daniel Ribeiro
1
Les parties qui dépendent de symfony (contrôleurs, modèles, modèles, etc ...) seront toujours couplées à symfony, puisque vous l'utilisez (extension de classes, utilisation d'aides, etc ...). Les classes qui fonctionnent seules seront dans l'espace de noms Company, et vous pouvez les charger à l'aide du conteneur de dépendances. Ces classes peuvent être indépendantes du framework.
miguel_ibero
1
Le fait est que le concept de Bundleva directement sur le partage public. Lorsque j'écris une application, je ne veux pas partager mon code, sauf pour les parties que j'ai intentionnellement construites en tant que modules communautaires. Ai-je tort?
Daniel Ribeiro
Vous n'avez pas à partager les bundles. Considérez un ensemble comme un groupe de classes avec une certaine configuration. Dans chaque projet, vous pouvez avoir différents bundles.
miguel_ibero
Vous devriez lire le livre symfony
miguel_ibero
5

Comme cela fait déjà 5 ans, voici quelques autres articles sur les bundles Symfony.

  1. Que sont les bundles dans Symfony? par Iltar van der Berg.

TLDR:

Avez-vous besoin de plusieurs bundles directement dans votre application? Préférablement pas. Il vaut mieux écrire un AppBundle pour éviter un spaghetti de dépendances. Vous pouvez simplement suivre les meilleures pratiques et cela fonctionnera bien.

  1. Symfony: Comment grouper par Toni Uebernickel.

TLDR:

Créez un seul ensemble appelé AppBundle pour votre logique d'application. Un AppBundle - mais s'il vous plaît ne mettez pas votre logique d'application là-dedans!

Reshat Belyalov
la source
-2

Le framework Symfony est très bon pour lancer rapidement une preuve de concept et tout le code peut entrer dans l'application de bundle par défaut dans src /

Dans ce bundle, vous pouvez structurer votre code comme vous le souhaitez.

Après, si vous souhaitez utiliser une autre technologie pour développer votre POC, vous pouvez facilement le traduire car vous ne structurez pas tout votre code dans la conception de bundle.

Pour tout le concept, vous n'avez pas extrémisé cela. Bundle est bon, mais tout et tous les jours ne sont pas bons.

Vous pouvez peut-être utiliser un Silex (micro framework Symfony) pour développer votre Proof of Concept afin de réduire l'impact des bundles tiers.

darkomen
la source