Tudo deveria realmente ser um pacote no Symfony 2.x?

205

Estou ciente de perguntas como essa , nas quais as pessoas tendem a discutir o conceito geral de pacote Symfony 2.

O problema é que, em um aplicativo específico, como, por exemplo, um aplicativo semelhante ao twitter, tudo deveria estar dentro de um pacote genérico, como dizem os documentos oficiais ?

A razão pela qual estou perguntando isso é porque, quando desenvolvemos aplicativos, em geral, não queremos associar altamente nosso código a uma estrutura de cola de pilha completa.

Se eu desenvolver um aplicativo baseado no Symfony 2 e, em algum momento, eu decidir que o Symfony 2 não é realmente a melhor opção para manter o desenvolvimento , isso será um problema para mim?

Portanto, a questão geral é: por que tudo é um pacote bom?

EDIT # 1

Há quase um ano, desde que fiz essa pergunta, escrevi um artigo para compartilhar meu conhecimento sobre esse tópico.

Daniel Ribeiro
fonte
1
Este é apenas um comentário, não uma resposta. Eu pessoalmente acho que devemos escolher a estrutura cuidadosamente antes de iniciar o projeto. Cada estrutura tem sua própria maneira de fazer as coisas, por isso fornecerá ferramentas para suportar dessa maneira o melhor. Se gostamos assim, seguimos. Existem outras opções por aí. Não queremos usar uma faca para cortar a madeira em vez de uma serra. Mas é uma pergunta muito interessante que você posou :)
Anh Nguyen

Respostas:

219

Eu escrevi uma postagem de blog mais completa e atualizada sobre este tópico: http://elnur.pro/symfony-without-bundles/


Não, nem tudo precisa estar em um pacote. Você poderia ter uma estrutura como esta:

  • src/Vendor/Model - para modelos,
  • src/Vendor/Controller - para controladores,
  • src/Vendor/Service - por serviços,
  • src/Vendor/Bundle - para pacotes, como src/Vendor/Bundle/AppBundle ,
  • etc.

Dessa forma, você colocaria AppBundleapenas as coisas que são realmente específicas do Symfony2. Se você decidir mudar para outra estrutura mais tarde, você se livrará doBundle espaço para nome e o substituirá pelo material escolhido.

Observe que o que estou sugerindo aqui é para código específico do aplicativo . Para pacotes reutilizáveis, ainda sugiro usar as práticas recomendadas .

Mantendo entidades fora de pacotes configuráveis

Para manter entidades src/Vendor/Modelfora de qualquer pacote, alterei a doctrineseção config.ymlde

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

para

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

Os nomes das entidades - para acessar dos repositórios do Doctrine - começam com Model neste caso, por exemplo Model:User,.

Você pode usar subespaços de nome para agrupar entidades relacionadas, por exemplo src/Vendor/User/Group.php,. Nesse caso, o nome da entidade éModel:User\Group .

Manter os controladores fora dos pacotes configuráveis

Primeiro, você precisa informar ao JMSDiExtraBundle para verificar a srcpasta em busca de serviços adicionando isso a config.yml:

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

Em seguida, defina controladores como serviços e coloque-os no Controllerespaço para nome:

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

Observe que estou usando meu ElnurAbstractControllerBundle para simplificar a definição de controladores como serviços.

A última coisa que resta é dizer ao Symfony para procurar modelos sem pacotes. Eu faço isso substituindo o serviço de adivinhador de modelos, mas como a abordagem é diferente entre o Symfony 2.0 e 2.1, estou fornecendo versões para os dois.

Substituindo o adivinho de modelo do Symfony 2.1+

Eu criei um pacote que faz isso para você.

Substituindo o ouvinte de modelo do Symfony 2.0

Primeiro, defina a 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;
        }
    }
}

E então diga ao Symfony para usá-lo adicionando isto a config.yml:

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

Usando modelos sem pacotes configuráveis

Agora, você pode usar modelos fora dos pacotes configuráveis. Mantenha-os na app/Resources/viewspasta. Por exemplo, os modelos para essas duas ações do controlador de exemplo acima estão localizados em:

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

Ao se referir a um modelo, apenas omita a parte do pacote configurável:

{% include ':Controller:view.html.twig' %}
Elnur Abdurrakhimov
fonte
2
Essa é realmente uma abordagem realmente interessante. Com isso, também posso desenvolver pacotes reais que contêm um conjunto específico de recursos que a comunidade pode usar, sem acoplar meu aplicativo ao próprio framework.
Daniel Ribeiro
57
Para tornar o código que você compartilha com a comunidade também não associado ao Symfony2, você pode colocar o material geral em uma biblioteca e criar um pacote que integre essa biblioteca ao Symfony2.
Elnur Abdurrakhimov 03/04
9
Essa é uma ideia interessante, desde que você não confie em nenhum dos comandos de geração de código. generate:doctrine:crudpor exemplo, espera que a entidade (= modelo no caso de elnur) esteja dentro de um pacote para funcionar.
Gala #
2
Com essa abordagem, existe alguma maneira de recuperar a funcionalidade da interface do aplicativo / console da CLI? Adoro a idéia de manter meus modelos em um local fora de qualquer pacote, mas gostaria de manter o acesso à funcionalidade da CLI.
Andy Baird
3
Isso deve ser colocado em um pacote :) #
31000 d0001
20

Claro que você pode desacoplar seu aplicativo. Apenas desenvolva-o como uma biblioteca e integre-o à vendor/pasta symfony (usando o depsou composer.json, dependendo de como você usa o Symfony2.0 ou o Symfony2.1). No entanto, você precisa de pelo menos um pacote, que atua como o "frontend" da sua biblioteca, onde o Symfony2 encontra o controlador (e tal).

KingCrunch
fonte
2
Por causa da tag symfony-2.0, presumo que você use a versão 2.0 atual. Nesse caso, crie um repositório git onde você quiser e coloque tudo nele, o que você deseja desenvolver independentemente do symfony. Em seu projeto symfony, atualize seu depsarquivo como mencionado aqui symfony.com/doc/current/cookbook/workflow/… Em seguida, crie um (ou mais) pacote (s) de aplicativos ( php app/console generate:bundle) para as coisas específicas do symfony.
KingCrunch
11

Uma distribuição symfony usual pode funcionar sem nenhum pacote extra (aplicativo), dependendo de quantas funcionalidades você deseja usar da estrutura de pilha completa.

Por exemplo, seus controladores podem ser chamados e podem ser colocados em qualquer lugar da estrutura do projeto, assim que forem carregados automaticamente.

Em um arquivo de definição de roteamento, você pode usar:

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

Pode ser qualquer objeto php antigo simples, apenas vinculado à estrutura pelo fato de ter que retornar um Symfony\Component\HttpFoundation\Responseobjeto.

Seus modelos de galho (ou outros) podem ser colocados como app/Resources/views/template.html.twige podem ser renderizados usando o ::template.html.twignome lógico.

Todos os serviços de DI podem ser definidos em app / config / config.yml (ou importados de, app/config/services.ymlpor exemplo, e todas as classes de serviço também podem ser quaisquer objetos php antigos simples. Não estão vinculados à estrutura.

Tudo isso é fornecido por padrão pela estrutura do symfony full stack.

O problema é quando você deseja usar arquivos de tradução (como xliff), porque eles são descobertos apenas por meio de pacotes configuráveis .

A distribuição symfony-light visa resolver esse tipo de problema, descobrindo tudo o que normalmente seria descoberto apenas por meio de pacotes.

Florian Klein
fonte
5

Você pode usar o KnpRadBundle , que tenta simplificar a estrutura do projeto.

Outra abordagem é usar, src/Company/Bundle/FrontendBundlepor exemplo, para os pacotes configuráveis ​​e src/Company/Stuff/Class.phpas classes que são independentes do symfony e que podem ser reutilizadas fora do framework

miguel_ibero
fonte
Mas então eu estaria acoplando o aplicativo ao KnpRadBundle ... Não existe uma abordagem mais fácil sobre esse assunto?
Daniel Ribeiro
1
As partes que dependem do symfony (controladores, modelos, modelos, etc ...) sempre serão acopladas ao symfony, desde que você o esteja usando (estendendo classes, usando auxiliares, etc ...). As classes que funcionam sozinhas estarão no espaço de nomes da empresa e você poderá carregá-las usando o contêiner de dependência. Essas classes podem ser independentes da estrutura.
Miguel_ibero
1
A questão é que o conceito de Bundleir diretamente para o compartilhamento público. Quando escrevo algum aplicativo, não quero compartilhar meu código, exceto aquelas partes que eu intencionalmente construí como módulos orientados pela comunidade. Estou errado?
Daniel Ribeiro
Você não precisa compartilhar os pacotes. Pense em um pacote configurável como um grupo de classes com alguma configuração. Em cada projeto, você pode ter diferentes pacotes configuráveis.
Miguel_ibero
Você deve ler o livro symfony
miguel_ibero 3/12/12
5

Como já se passaram 5 anos, aqui estão mais alguns artigos sobre os Pacotes Symfony.

  1. O que são pacotes no Symfony? de Iltar van der Berg.

TLDR:

Você precisa de vários pacotes configuráveis ​​diretamente no seu aplicativo? Mais provável que não. É melhor escrever um AppBundle para evitar um espaguete de dependências. Você pode simplesmente seguir as melhores práticas e funcionará bem.

  1. Symfony: Como agrupar por Toni Uebernickel.

TLDR:

Crie apenas um pacote chamado AppBundle para sua lógica de aplicativo. Um AppBundle - mas por favor, não coloque a lógica da sua aplicação lá!

Reshat Belyalov
fonte
-2

O framework Symfony é muito bom para iniciar rapidamente uma prova de conceito e todo o código pode ser inserido no aplicativo de pacote padrão no src /

Neste pacote, você pode estruturar seu código como desejar.

Depois, se você quiser usar outra tecnologia para desenvolver seu POC, poderá traduzi-lo facilmente porque não estrutura todo o seu código na concepção de pacote.

Para todo o conceito, você não extremista isso. O pacote é bom, mas inclui tudo e todos os dias não é bom.

Talvez você possa usar um Silex (micro framework Symfony) para desenvolver sua Prova de Conceito e reduzir o impacto de terceiros do pacote.

darkomen
fonte