Sollte in Symfony 2.x wirklich alles ein Bundle sein?

205

Mir sind Fragen wie diese bekannt , bei denen die Leute dazu neigen, das allgemeine Symfony 2-Konzept des Bundles zu diskutieren.

Die Sache ist, in einer bestimmten Anwendung, wie zum Beispiel einer Twitter-ähnlichen Anwendung, sollte sich wirklich alles in einem generischen Bundle befinden, wie die offiziellen Dokumente sagen?

Der Grund, warum ich dies frage, ist, dass wir bei der Entwicklung von Anwendungen im Allgemeinen unseren Code nicht in hohem Maße an ein Full-Stack-Kleberahmenwerk koppeln möchten.

Wenn ich eine Symfony 2-basierte Anwendung entwickle und irgendwann beschließe, dass Symfony 2 nicht wirklich die beste Wahl ist, um die Entwicklung am Laufen zu halten , ist das ein Problem für mich?

Die allgemeine Frage lautet also: Warum ist alles, was ein Bündel ist, eine gute Sache?

EDIT # 1

Fast ein Jahr, seit ich diese Frage gestellt habe, habe ich einen Artikel geschrieben , um mein Wissen zu diesem Thema zu teilen.

Daniel Ribeiro
quelle
1
Dies ist nur ein Kommentar, keine Antwort. Ich persönlich denke, wir sollten das Framework sorgfältig auswählen, bevor wir mit dem Projekt beginnen. Jedes Framework hat seine eigene Art, Dinge zu erledigen, daher bietet es Tools, um diese auf diese Weise optimal zu unterstützen. Wenn wir so mögen, folgen wir. Es gibt andere Möglichkeiten da draußen. Wir wollen kein Messer verwenden, um das Holz anstelle einer Säge zu schneiden. Aber es ist eine sehr interessante Frage, die Sie gestellt haben :)
Anh Nguyen

Antworten:

219

Ich habe einen ausführlicheren und aktualisierten Blog-Beitrag zu diesem Thema geschrieben: http://elnur.pro/symfony-without-bundles/


Nein, nicht alles muss in einem Bündel sein. Sie könnten eine Struktur wie diese haben:

  • src/Vendor/Model - für Modelle,
  • src/Vendor/Controller - für Steuerungen,
  • src/Vendor/Service - für Dienstleistungen,
  • src/Vendor/Bundle- für Bündel, wie src/Vendor/Bundle/AppBundle,
  • etc.

Auf diese Weise würden Sie in die setzen AppBundle nur das Zeug einfügen, das wirklich Symfony2-spezifisch ist. Wenn Sie später zu einem anderen Framework wechseln, wird der BundleNamespace entfernt und durch das ausgewählte Framework ersetzt.

Bitte beachten Sie, dass ich hier einen app- spezifischen Code vorschlage . Für wiederverwendbare Bundles empfehle ich weiterhin die Verwendung der Best Practices .

Entitäten aus Bündeln heraushalten

Um Entitäten src/Vendor/Modelaußerhalb eines Bundles zu halten, habe ich den doctrineAbschnitt in config.ymlvon geändert

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

zu

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

Die Namen der Entitäten - für den Zugriff über Doctrine-Repositories - beginnen Modelin diesem Fall beispielsweise mitModel:User .

Sie können Subnamespaces verwenden, um beispielsweise verwandte Entitäten zu gruppieren src/Vendor/User/Group.php. In diesem Fall lautet der Name der Entität Model:User\Group.

Controller von Bündeln fernhalten

Zunächst müssen Sie JMSDiExtraBundle anweisen , den srcOrdner nach Diensten zu durchsuchen , indem Sie Folgendes hinzufügen config.yml:

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

Anschließend definieren Sie Controller als Services und fügen sie unter den ControllerNamespace ein:

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

Beachten Sie, dass ich mein ElnurAbstractControllerBundle verwende , um die Definition von Controllern als Dienste zu vereinfachen.

Als letztes müssen Sie Symfony anweisen, nach Vorlagen ohne Bundles zu suchen. Dazu überschreibe ich den Template Guesser-Dienst. Da sich der Ansatz zwischen Symfony 2.0 und 2.1 unterscheidet, stelle ich Versionen für beide bereit.

Überschreiben des Symfony 2.1+ Template Guesser

Ich habe ein Bundle erstellt , das das für Sie erledigt.

Überschreiben des Symfony 2.0-Vorlagen-Listeners

Definieren Sie zunächst die Klasse:

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

Und dann weisen Sie Symfony an, es zu verwenden, indem Sie Folgendes hinzufügen config.yml:

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

Vorlagen ohne Bundles verwenden

Jetzt können Sie Vorlagen aus Bundles verwenden. Bewahren Sie sie unter dem app/Resources/viewsOrdner auf. Vorlagen für diese beiden Aktionen des obigen Beispielcontrollers befinden sich beispielsweise in:

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

Wenn Sie auf eine Vorlage verweisen, lassen Sie einfach den Bundle-Teil weg:

{% include ':Controller:view.html.twig' %}
Elnur Abdurrakhimov
quelle
2
Das ist eigentlich ein wirklich interessanter Ansatz. Damit kann ich auch echte Bundles entwickeln, die bestimmte Funktionen enthalten, die die Community verwenden kann, ohne meine Anwendung kaum an das Framework selbst zu koppeln.
Daniel Ribeiro
57
Um den Code, den Sie für die Community freigeben, nicht auch an Symfony2 zu koppeln, können Sie die allgemeinen Inhalte in eine Bibliothek einfügen und anschließend ein Bundle erstellen, das diese Bibliothek in Symfony2 integriert.
Elnur Abdurrakhimov
9
Dies ist eine interessante Idee, solange Sie sich nicht auf einen der Befehle zur Codegenerierung verlassen. generate:doctrine:cruderwartet beispielsweise, dass sich die Entität (= Modell in elnurs Fall) innerhalb eines Bündels befindet, um zu funktionieren.
Geca
2
Gibt es mit diesem Ansatz eine Möglichkeit, die Funktionalität der CLI-App / Konsolen-Schnittstelle wiederherzustellen? Ich mag die Idee, meine Modelle an einem Ort außerhalb eines Bundles zu halten, aber ich möchte den Zugriff auf die CLI-Funktionalität behalten.
Andy Baird
3
Dies sollte in ein Bündel gelegt werden :)
d0001
20

Natürlich können Sie Ihre Bewerbung entkoppeln. Entwickeln Sie es einfach als Bibliothek und integrieren Sie es in den Symfony- vendor/Ordner (entweder mit depsoder composer.json, je nachdem, ob Sie Symfony2.0 oder Symfony2.1 verwenden). Sie benötigen jedoch mindestens ein Bundle, das als "Frontend" Ihrer Bibliothek fungiert, in dem Symfony2 den Controller (und dergleichen) findet.

KingCrunch
quelle
2
Aufgrund des Tags symfony-2.0gehe ich davon aus, dass Sie die aktuelle Version 2.0 verwenden. In diesem Fall erstellen Sie ein Git-Repository, wo immer Sie möchten, und fügen Sie alles ein, was Sie unabhängig von Symfony entwickeln möchten. Aktualisieren Sie in Ihrem Symfony-Projekt Ihre deps-Datei wie hier erwähnt symfony.com/doc/current/cookbook/workflow/…. Erstellen Sie dann einfach ein (oder mehrere) Anwendungspakete (n) ( php app/console generate:bundle) für das symfony-spezifische Material.
KingCrunch
11

Eine übliche Symfony-Distribution kann ohne zusätzliches (Anwendungs-) Bundle funktionieren, je nachdem, wie viele Funktionen Sie vom Full-Stack-Framework verwenden möchten.

Zum Beispiel können Ihre Controller beliebig aufrufbar sein, die an einer beliebigen Stelle in Ihrer Projektstruktur platziert werden können, sobald sie automatisch geladen werden.

In einer Routing-Definitionsdatei können Sie Folgendes verwenden:

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

Es kann jedes einfache alte PHP-Objekt sein, das nur durch die Tatsache an das Framework gebunden ist, dass es a zurückgeben muss Symfony\Component\HttpFoundation\Response Objekt zurückgeben muss.

Ihre Zweigvorlagen (oder andere) können wie app/Resources/views/template.html.twigfolgt platziert und mithilfe von gerendert werden::template.html.twig logischen Namens .

Alle DI-Dienste können in app / config / config.yml definiert (oder aus importiert werden) werden app/config/services.yml , und alle Dienstklassen können auch einfache alte PHP-Objekte sein, die überhaupt nicht an das Framework gebunden sind.

All dies wird standardmäßig vom symfony Full Stack Framework bereitgestellt.

Wo werden Sie Probleme haben, wenn Sie Übersetzungsdateien verwenden wollen (wie XLIFF), weil sie durch Bündel entdeckt werden nur .

Die Symfony-Light- Verteilung zielt darauf ab, diese Art von Problemen zu lösen, indem alles entdeckt wird, was normalerweise nur durch Bündel entdeckt wird.

Florian Klein
quelle
5

Sie könnten KnpRadBundle verwenden , um die Projektstruktur zu vereinfachen.

Ein anderer Ansatz besteht darin, ihn src/Company/Bundle/FrontendBundlebeispielsweise für die Bundles und src/Company/Stuff/Class.phpfür Klassen zu verwenden, die symfonieunabhängig sind und außerhalb des Frameworks wiederverwendet werden könnten

miguel_ibero
quelle
Aber dann würde ich die Anwendung an das KnpRadBundle koppeln ... Gibt es in dieser Angelegenheit keinen einfacheren Ansatz?
Daniel Ribeiro
1
Die Teile, die von Symfony abhängen (Controller, Modelle, Vorlagen usw.), werden immer an Symfony gekoppelt, da Sie es verwenden (Klassen erweitern, Helfer verwenden usw.). Die Klassen, die alleine arbeiten, befinden sich im Firmennamensraum, und Sie können sie mithilfe des Abhängigkeitscontainers laden. Diese Klassen können rahmenunabhängig sein.
Miguel_ibero
1
Die Sache ist, das Konzept von Bundlegeht direkt auf das öffentliche Teilen. Wenn ich eine Anwendung schreibe, möchte ich meinen Code nicht freigeben, außer den Teilen, die ich absichtlich als Community-gesteuerte Module erstellt habe. Liege ich falsch?
Daniel Ribeiro
Sie müssen die Bundles nicht teilen. Stellen Sie sich ein Bundle als eine Gruppe von Klassen mit einer bestimmten Konfiguration vor. In jedem Projekt können Sie verschiedene Bundles haben.
Miguel_ibero
Sie sollten das Symfony-Buch
miguel_ibero
5

Da bereits 5 Jahre vergangen sind, finden Sie hier einige weitere Artikel zu Symfony Bundles.

  1. Was sind Bundles in Symfony? von Iltar van der Berg.

TLDR:

Benötigen Sie mehrere Bundles direkt in Ihrer Anwendung? Höchst wahrscheinlich nicht. Sie sollten besser ein AppBundle schreiben, um Spaghetti von Abhängigkeiten zu vermeiden. Sie können einfach die Best Practices befolgen und es wird gut funktionieren.

  1. Symfony: How to Bundle von Toni Uebernickel.

TLDR:

Erstellen Sie nur ein Bundle namens AppBundle für Ihre Anwendungslogik. Ein AppBundle - aber bitte geben Sie Ihre Anwendungslogik nicht ein!

Reshat Belyalov
quelle
-2

Das Symfony-Framework eignet sich sehr gut, um schnell einen Proof of Concept zu starten, und der gesamte Code kann in die Standard-Bundle-Anwendung in src / eingegeben werden

In diesem Bundle können Sie Ihren Code nach Ihren Wünschen strukturieren.

Wenn Sie andere Technologien für die Entwicklung Ihres POC verwenden möchten, können Sie diese problemlos übersetzen, da Sie nicht den gesamten Code in der Bundle-Konzeption strukturieren.

Bei allem Konzept haben Sie dies nicht extremisiert. Bündel ist gut, aber alles bündeln und der Alltag ist nicht gut.

Möglicherweise können Sie ein Silex (Symfony Micro Framework) verwenden, um Ihren Proof of Concept zu entwickeln und die Auswirkungen von Bundle-Drittanbietern zu verringern.

Darkomen
quelle