¿Debería todo ser realmente un paquete en Symfony 2.x?

205

Soy consciente de preguntas como esta , donde las personas tienden a discutir el concepto general de paquete de Symfony 2.

La cuestión es que, en una aplicación específica, como, por ejemplo, una aplicación similar a Twitter, ¿debería todo estar realmente dentro de un paquete genérico, como dicen los documentos oficiales ?

La razón por la que pregunto esto es porque cuando desarrollamos aplicaciones, en general, no queremos acoplar nuestro código a un marco de pegamento de pila completa.

Si desarrollo una aplicación basada en Symfony 2 y, en algún momento, decido que Symfony 2 no es realmente la mejor opción para mantener el desarrollo , ¿será un problema para mí?

Entonces, la pregunta general es: ¿por qué todo ser un paquete es algo bueno?

EDITAR # 1

Hace casi un año desde que hice esta pregunta, escribí un artículo para compartir mis conocimientos sobre este tema.

Daniel Ribeiro
fuente
1
Esto es solo un comentario, no una respuesta. Personalmente, creo que debemos elegir el marco cuidadosamente antes de comenzar el proyecto. Cada marco tiene su propia forma de hacer cosas, por lo que proporcionará herramientas para apoyar de esa manera lo mejor. Si nos gusta así, lo seguimos. Hay otras opciones por ahí. No queremos usar un cuchillo para cortar la madera en lugar de una sierra. Pero es una pregunta muy interesante que planteaste :)
Anh Nguyen

Respuestas:

219

He escrito una publicación de blog más completa y actualizada sobre este tema: http://elnur.pro/symfony-without-bundles/


No, no todo tiene que estar en un paquete. Podrías tener una estructura como esta:

  • src/Vendor/Model - para modelos,
  • src/Vendor/Controller - para controladores,
  • src/Vendor/Service - para servicios,
  • src/Vendor/Bundle- para los paquetes, como src/Vendor/Bundle/AppBundle,
  • etc.

De esta manera, pondrías lo AppBundleúnico que es realmente específico de Symfony2. Si decide cambiar a otro marco más adelante, eliminará el Bundleespacio de nombres y lo reemplazará con el marco elegido.

Tenga en cuenta que lo que sugiero aquí es para el código específico de la aplicación . Para paquetes reutilizables, todavía sugiero usar las mejores prácticas .

Mantener entidades fuera de paquetes

Para mantener las entidades src/Vendor/Modelfuera de cualquier paquete, he cambiado la doctrinesección config.ymlde

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

a

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

Los nombres de las entidades, para acceder desde los repositorios de Doctrine, comienzan Modelen este caso, por ejemplo Model:User,.

Puede utilizar subespacios a entidades relacionadas con el grupo en conjunto, por ejemplo, src/Vendor/User/Group.php. En este caso, el nombre de la entidad es Model:User\Group.

Mantener a los controladores fuera de los paquetes

Primero, debe decirle a JMSDiExtraBundle que escanee la srccarpeta en busca de servicios agregando esto a config.yml:

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

Luego define los controladores como servicios y los coloca bajo el Controllerespacio de nombres:

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

Tenga en cuenta que estoy usando mi ElnurAbstractControllerBundle para simplificar la definición de controladores como servicios.

Lo último que queda es decirle a Symfony que busque plantillas sin paquetes. Hago esto anulando el servicio de adivinanzas de plantilla, pero dado que el enfoque es diferente entre Symfony 2.0 y 2.1, proporciono versiones para ambos.

Anular el adivinador de plantillas de Symfony 2.1+

He creado un paquete que hace eso por ti.

Anular el oyente de la plantilla Symfony 2.0

Primero, defina la clase:

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

Y luego dile a Symfony que lo use agregando esto a config.yml:

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

Usar plantillas sin paquetes

Ahora, puede usar plantillas de paquetes. Mantenlos debajo de la app/Resources/viewscarpeta. Por ejemplo, las plantillas para esas dos acciones del controlador de ejemplo anterior se encuentran en:

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

Cuando se refiera a una plantilla, simplemente omita la parte del paquete:

{% include ':Controller:view.html.twig' %}
Elnur Abdurrakhimov
fuente
2
Ese es realmente un enfoque realmente interesante. Con eso, también puedo desarrollar paquetes reales que contienen un conjunto específico de características que la comunidad puede usar, sin apenas acoplar mi aplicación al marco en sí.
Daniel Ribeiro
57
Para hacer que el código que comparte con la comunidad no se asocie también a Symfony2, puede poner el contenido general en una biblioteca y luego crear un paquete que integre esa biblioteca con Symfony2.
Elnur Abdurrakhimov
9
Esta es una idea interesante siempre que no confíe en ninguno de los comandos de generación de código. generate:doctrine:crudpor ejemplo, espera que la entidad (= modelo en el caso de elnur) esté dentro de un paquete para funcionar.
geca
2
Con este enfoque, ¿hay alguna forma de recuperar la funcionalidad de la interfaz CLI de la aplicación / consola? Me encanta la idea de mantener mis modelos en un lugar fuera de cualquier paquete, pero me gustaría conservar el acceso a la funcionalidad CLI.
Andy Baird
3
Esto debe ponerse en un paquete :)
d0001
20

Por supuesto, puede desacoplar su aplicación. Simplemente desarrolle como una biblioteca e vendor/intégrelo en la carpeta de Symfony (ya sea usando depso composer.json, dependiendo de si usa Symfony2.0 o Symfony2.1). Sin embargo, necesita al menos un paquete, que actúa como la "interfaz" de su biblioteca, donde Symfony2 encuentra el controlador (y tal).

KingCrunch
fuente
2
Debido a la etiqueta symfony-2.0, supondré que usa la versión 2.0 actual. En este caso, crea un repositorio de git donde quieras y pon todo en él, lo que quieras desarrollar independientemente de Symfony. En su proyecto de Symfony, actualice su depsarchivo como se menciona aquí symfony.com/doc/current/cookbook/workflow/… Luego, simplemente cree uno (o más) paquete (s) de aplicaciones ( php app/console generate:bundle) para el material específico de Symfony.
KingCrunch
11

Una distribución habitual de Symfony puede funcionar sin ningún paquete adicional (aplicación), dependiendo de la cantidad de funcionalidades que desee utilizar desde el marco de la pila completa.

Por ejemplo, sus controladores pueden ser invocables y pueden colocarse en cualquier lugar de la estructura de su proyecto, tan pronto como se carguen automáticamente.

En un archivo de definición de enrutamiento, puede usar:

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

Puede ser cualquier objeto php antiguo simple, solo vinculado al marco por el hecho de que tiene que devolver un Symfony\Component\HttpFoundation\Responseobjeto.

Sus plantillas de ramita (u otras) se pueden poner como app/Resources/views/template.html.twigy se pueden representar con el ::template.html.twignombre lógico.

Todos los servicios DI pueden definirse en app / config / config.yml (o importarse de, app/config/services.ymlpor ejemplo, y todas las clases de servicio pueden ser también cualquier objeto php antiguo, no vinculado al marco en absoluto).

Todo esto lo proporciona de forma predeterminada el framework de pila completa de Symfony.

Donde tendrá problemas es cuando desee utilizar archivos de traducción (como xliff), porque se descubren solo a través de paquetes .

La distribución Symfony-Light tiene como objetivo resolver este tipo de problemas descubriendo todo lo que generalmente se descubriría solo a través de paquetes.

Florian Klein
fuente
5

Podría usar KnpRadBundle , que intenta simplificar la estructura del proyecto.

Otro enfoque es usar, src/Company/Bundle/FrontendBundlepor ejemplo, para los paquetes y src/Company/Stuff/Class.phppara las clases que son independientes de Symfony y que podrían reutilizarse fuera del marco

miguel_ibero
fuente
Pero luego estaría acoplando la aplicación al KnpRadBundle ... ¿No hay un enfoque más fácil sobre este asunto?
Daniel Ribeiro
1
Las partes que dependen de Symfony (controladores, modelos, plantillas, etc.) siempre estarán acopladas a Symfony, ya que lo está utilizando (ampliando clases, utilizando ayudantes, etc.). Las clases que funcionan solas estarán en el espacio de nombres de la Compañía, y puede cargarlas usando el contenedor de dependencias. Estas clases pueden ser independientes del marco.
miguel_ibero
1
La cuestión es que el concepto de Bundleva directamente a compartir públicamente. Cuando escribo alguna aplicación, no quiero compartir mi código, excepto aquellas partes que construí intencionalmente como módulos dirigidos por la comunidad. ¿Me equivoco?
Daniel Ribeiro
No tienes que compartir los paquetes. Piense en un paquete como en un grupo de clases con alguna configuración. En cada proyecto puede tener diferentes paquetes.
miguel_ibero
Deberías leer el libro de Symfony
miguel_ibero
5

Como ya han pasado 5 años, aquí hay algunos artículos más sobre Symfony Bundles.

  1. ¿Qué son los paquetes en Symfony? por Iltar van der Berg.

TLDR:

¿Necesita múltiples paquetes en su aplicación directamente? Probablemente no. Es mejor escribir un AppBundle para evitar un espagueti de dependencias. Simplemente puede seguir las mejores prácticas y funcionará bien.

  1. Symfony: Cómo combinar por Toni Uebernickel.

TLDR:

Cree solo un paquete llamado AppBundle para la lógica de su aplicación. One AppBundle, ¡pero no coloque la lógica de su aplicación allí!

Reshat Belyalov
fuente
-2

El framework Symfony es muy bueno para lanzar rápidamente una prueba de concepto y todo el código puede ingresar dentro de la aplicación de paquete predeterminada en src /

En este paquete, puede estructurar su código como desee.

Después, si desea utilizar otra tecnología para desarrollar su POC, puede traducirlo fácilmente porque no estructura todo su código en la concepción de paquetes.

Para todo el concepto, no lo extremas. El paquete es bueno, pero lo incluye todo y todos los días no es bueno.

Quizás pueda usar un Silex (Symfony micro framework) para desarrollar su Prueba de concepto para reducir el impacto del paquete de terceros.

darkomen
fuente