Je suis conscient de questions telles que this , où les gens ont tendance à discuter du concept général de bundle de Symfony 2.
Le problème est que, dans une application spécifique, telle que, par exemple, une application de type Twitter, tout doit-il réellement être contenu dans un paquet générique, tel que le documentation officielle dire?
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 framework de collage complet.
Si je développe une application basée sur Symfony 2 et que, à un moment donné, je décide que Symfony 2 n'est pas vraiment le meilleur choix pour poursuivre le développement, cela me pose-t-il un problème?
La question générale est donc la suivante: pourquoi tout être groupé est-il une bonne chose?
EDIT # 1
Presque un an maintenant que je pose cette question, j’ai écrit un article pour partager mes connaissances sur ce sujet.
J'ai écrit un article de blog plus complet et actualisé sur ce sujet: http://elnur.pro/symfony-without-bundles/
Non, tout ne doit pas être dans un paquet. 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
,De cette façon, vous ne mettriez dans AppBundle
que ce qui est vraiment spécifique à Symfony2. Si vous décidez de passer ultérieurement à un autre framework, vous vous débarrassez de l'espace de noms Bundle
et le remplacez par les éléments de framework choisis.
S'il vous plaît noter que ce que je suggère ici est pour app code spécifique. Pour les paquets réutilisables, je suggère toujours d’utiliser les meilleures pratiques .
Pour conserver les entités dans src/Vendor/Model
En dehors de tout ensemble, j'ai modifié la section doctrine
de config.yml
De
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
Noms des entités - pour y accéder depuis Doctrine - commencez par Model
dans ce cas, par exemple, Model:User
.
Vous pouvez utiliser des espaces de sous-noms pour regrouper des entités liées, par exemple, src/Vendor/User/Group.php
. Dans ce cas, le nom de l'entité est Model:User\Group
.
Tout d’abord, vous devez dire à JMSDiExtraBundle d’analyser le dossier src
à la recherche de services en ajoutant ceci à config.yml
:
jms_di_extra:
locations:
directories: %kernel.root_dir%/../src
Ensuite, vous définissez les contrôleurs en tant que services et placez-les sous l'espace de noms 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
];
}
}
Notez que j'utilise mon ElnurAbstractControllerBundle pour simplifier la définition des contrôleurs en tant que services.
La dernière chose à faire est de dire à Symfony de rechercher des modèles sans bundles. Je le fais en surchargeant le service de modèle de devinette, mais comme l'approche est différente entre Symfony 2.0 et 2.1, je fournis des versions pour les deux.
J'ai créé un bundle qui le fait pour vous.
D'abord, définissez 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 dites ensuite à Symfony de l’utiliser en ajoutant ceci à config.yml
:
parameters:
jms_di_extra.template_listener.class: Vendor\Listener\TemplateListener
Maintenant, vous pouvez utiliser des modèles hors des ensembles. Conservez-les dans le dossier app/Resources/views
. Par exemple, les modèles pour 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 bundle:
{% include ':Controller:view.html.twig' %}
Bien sûr, vous pouvez découpler votre application. Développez-la simplement en tant que bibliothèque et intégrez-la dans le dossier symfony vendor/
- (soit en utilisant le deps
ou composer.json
, Selon que vous utilisez Symfony2.0 ou Symfony2.1) . Cependant, vous avez besoin d'au moins un paquet, qui fait office de "frontend" de votre bibliothèque, où Symfony2 trouve le contrôleur (et autres).
Une distribution symfony habituelle peut fonctionner sans aucun bundle (application) supplémentaire, en fonction du nombre de fonctionnalités que vous souhaitez utiliser à partir de la structure de pile complète.
Par exemple, vos contrôleurs peuvent être des objets appelables qui peuvent être placés 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 }
Ce peut être n'importe quel objet php ordinaire, uniquement lié au framework par le fait qu'il doit renvoyer un Symfony\Component\HttpFoundation\Response
objet.
Vos gabarits twig (ou autres) peuvent être mis comme app/Resources/views/template.html.twig
et peut être rendu avec le ::template.html.twig
nom logique.
Tous les services DI peuvent être définis dans app/config/config.yml (ou importés de app/config/services.yml
_ par exemple, et toutes les classes de service peuvent également être des objets php ordinaires. pas du tout lié au cadre.
Tout cela est fourni par défaut par la structure de pile complète de symfony.
Vous aurez des problèmes lorsque vous voudrez utiliser des fichiers de traduction (tels que xliff), car ils sont découverts via des bundles uniquement .
La distribution symfony-light a pour but de résoudre ce genre de problèmes en découvrant tout ce qui serait normalement découvert uniquement par le biais de bundles.
Depuis 5 ans déjà, voici quelques articles sur les bundles Symfony.
TLDR:
Avez-vous besoin de plusieurs bundles directement dans votre application? Préférablement pas. Il vaut mieux écrire un AppBundle pour éviter les spaghettis des dépendances. Vous pouvez simplement suivre les meilleures pratiques et cela fonctionnera bien.
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 ici!
Vous pouvez utiliser KnpRadBundle , qui tente de simplifier la structure du projet.
Une autre approche consiste à utiliser src/Company/Bundle/FrontendBundle
_ par exemple pour les bundles et src/Company/Stuff/Class.php
pour les classes indépendantes de symfony et qui pourraient être réutilisées en dehors du cadre