ทุกอย่างควรเป็นชุดใน Symfony 2.x จริงหรือ


205

ฉันตระหนักถึงคำถามเช่นนี้ซึ่งผู้คนมักจะพูดคุยเกี่ยวกับแนวคิดทั่วไปของกลุ่มผลิตภัณฑ์ Symfony 2

สิ่งที่อยู่ในแอพพลิเคชั่นที่เฉพาะเจาะจงเช่นแอพพลิเคชั่นที่เหมือนทวิตเตอร์ทุกอย่างควรจะอยู่ในกลุ่มสามัญอย่างที่เอกสารทางการบอกหรือไม่

เหตุผลที่ฉันถามสิ่งนี้เพราะเมื่อเราพัฒนาแอปพลิเคชั่นโดยทั่วไปเราไม่ต้องการจับคู่โค้ดของเรากับกรอบกาวเต็มสแต็ค

ถ้าฉันพัฒนาแอพพลิเคชั่นที่ใช้ Symfony 2 และในบางจุดฉันคิดว่า Symfony 2 ไม่ใช่ตัวเลือกที่ดีที่สุดในการพัฒนาต่อไปนั่นจะเป็นปัญหาสำหรับฉันหรือไม่

ดังนั้นคำถามทั่วไปคือทำไมทุกอย่างเป็นกลุ่มเป็นสิ่งที่ดี?

แก้ไข # 1

เกือบหนึ่งปีแล้วตั้งแต่ฉันถามคำถามนี้ฉันเขียนบทความเพื่อแบ่งปันความรู้ของฉันในหัวข้อนี้


1
นี่เป็นเพียงความคิดเห็นไม่ใช่คำตอบ โดยส่วนตัวผมคิดว่าเราควรเลือกกรอบอย่างรอบคอบก่อนเริ่มโครงการ กรอบงานทุกชิ้นมีวิธีการของตนเองในการทำสิ่งต่าง ๆ ดังนั้นมันจึงมีเครื่องมือในการสนับสนุนวิธีที่ดีที่สุด ถ้าเราชอบแบบนั้นเราก็ทำตาม มีทางเลือกอื่น ๆ ออกมี เราไม่ต้องการใช้มีดตัดไม้แทนเลื่อย แต่มันเป็นคำถามที่น่าสนใจมาก ๆ ที่คุณโพสต์ :)
Anh Nguyen

คำตอบ:


219

ฉันเขียนบทความในบล็อกนี้อย่างละเอียดมากขึ้นและอัปเดตในหัวข้อนี้: http://elnur.pro/symfony-without-bundles/


ไม่ไม่ใช่ทุกอย่างจะต้องอยู่ในกลุ่ม คุณสามารถมีโครงสร้างแบบนี้:

  • src/Vendor/Model - สำหรับรุ่น
  • src/Vendor/Controller - สำหรับตัวควบคุม
  • src/Vendor/Service - สำหรับบริการ
  • src/Vendor/Bundle- สำหรับการรวมกลุ่มเช่นsrc/Vendor/Bundle/AppBundle,
  • เป็นต้น

ด้วยวิธีนี้คุณจะใส่AppBundleเฉพาะสิ่งที่เฉพาะ Symfony2 เท่านั้น หากคุณตัดสินใจที่จะเปลี่ยนเป็นเฟรมเวิร์กอื่นในภายหลังคุณจะต้องลบBundleเนมสเปซและแทนที่ด้วยเฟรมเวิร์กที่เลือก

โปรดทราบว่าสิ่งที่ฉันแนะนำที่นี่สำหรับรหัสเฉพาะแอพ สำหรับการรวมกลุ่มนำมาใช้ใหม่ก็ยังแนะนำให้ใช้ปฏิบัติที่ดีที่สุด

การป้องกันเอนทิตีออกจากบันเดิล

เพื่อให้หน่วยงานในsrc/Vendor/Modelด้านนอกของมัดใด ๆ ผมเคยเปลี่ยนdoctrineในส่วนconfig.ymlจาก

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

ชื่อของเอนทิตี - เพื่อเข้าถึงจากที่เก็บของ Doctrine - เริ่มต้นด้วยModelในกรณีนี้เช่นModel:Userในกรณีนี้ยกตัวอย่างเช่น

คุณสามารถใช้ subnamespaces src/Vendor/User/Group.phpไปยังกลุ่มหน่วยงานที่เกี่ยวข้องร่วมกันยกตัวอย่างเช่น Model:User\Groupในกรณีนี้ชื่อของกิจการคือ

ไม่ให้ตัวควบคุมออกจากชุดข้อมูล

ก่อนอื่นคุณต้องแจ้งให้JMSDiExtraBundleสแกนsrcโฟลเดอร์เพื่อรับบริการต่างๆโดยเพิ่มไปที่config.yml:

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

จากนั้นคุณกำหนดคอนโทรลเลอร์เป็นบริการและวางไว้ใต้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
        ];
    }
}

โปรดทราบว่าฉันใช้ElnurAbstractControllerBundleของฉันเพื่อลดความซับซ้อนของการกำหนดตัวควบคุมเป็นบริการ

สิ่งสุดท้ายที่เหลือคือการบอกให้ Symfony ค้นหาเทมเพลตที่ไม่มีบันเดิล ฉันทำสิ่งนี้โดยการเอาชนะบริการเดาแม่แบบ แต่เนื่องจากวิธีการนี้แตกต่างกันระหว่าง Symfony 2.0 และ 2.1 ฉันจึงให้บริการทั้งสองรุ่น

การเอาชนะตัวเดาเทมเพลต Symfony 2.1+

ฉันได้สร้างกลุ่มที่ทำเพื่อคุณ

การเอาชนะฟังแม่แบบ Symfony 2.0

ก่อนกำหนดคลาส:

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

จากนั้นบอก Symfony ให้ใช้งานโดยเพิ่มสิ่งนี้ลงในconfig.yml:

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

ใช้เทมเพลตที่ไม่มีบันเดิล

ตอนนี้คุณสามารถใช้เทมเพลตจากบันเดิล เก็บไว้ในapp/Resources/viewsโฟลเดอร์ ตัวอย่างเช่นเทมเพลตสำหรับการกระทำสองอย่างจากตัวควบคุมตัวอย่างด้านบนอยู่ใน:

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

เมื่ออ้างถึงเทมเพลตเพียงแค่ละเว้นส่วนบันเดิล:

{% include ':Controller:view.html.twig' %}

2
นั่นเป็นวิธีที่น่าสนใจจริงๆ ด้วยสิ่งนี้ฉันยังสามารถพัฒนาบันเดิลจริงที่มีชุดคุณลักษณะเฉพาะที่ชุมชนสามารถใช้ได้โดยไม่ต้องเชื่อมโยงแอปพลิเคชันของฉันเข้ากับกรอบงาน
Daniel Ribeiro

57
ในการทำให้โค้ดที่คุณแบ่งปันกับชุมชนไม่ได้อยู่คู่กับ Symfony2 เช่นกันคุณสามารถใส่สิ่งทั่วไปลงในไลบรารีแล้วสร้างกลุ่มที่รวมไลบรารีนั้นเข้ากับ Symfony2
Elnur Abdurrakhimov

9
นี่เป็นแนวคิดที่น่าสนใจตราบใดที่คุณไม่ต้องพึ่งพาคำสั่งการสร้างรหัสใด ๆ generate:doctrine:crudเช่นคาดว่าเอนทิตี (= แบบจำลองในกรณีของ elnur) จะอยู่ในกลุ่มเพื่อทำงาน
geca

2
ด้วยวิธีนี้จะมีวิธีใดบ้างที่จะได้รับการทำงานของแอป / คอนโซลอินเตอร์เฟส CLI ฉันชอบความคิดที่จะรักษาแบบจำลองของฉันไว้ในที่ที่ไม่ได้อยู่ในกลุ่มบันเดิลใด ๆ
Andy Baird

3
สิ่งนี้ควรใส่เข้าไปในมัด :)
d0001

20

แน่นอนคุณสามารถแยกการสมัครของคุณ เพียงพัฒนาเป็นไลบรารีและรวมเข้ากับ symfony vendor/-folder (โดยใช้depsหรือcomposer.jsonขึ้นอยู่กับว่าคุณใช้ Symfony2.0 หรือ Symfony2.1) อย่างไรก็ตามคุณต้องมีบันเดิลอย่างน้อยหนึ่งชุดซึ่งทำหน้าที่เป็น "ส่วนหน้า" ของไลบรารีซึ่ง Symfony2 ค้นหาคอนโทรลเลอร์ (และเช่นนั้น)


2
เนื่องจากแท็กsymfony-2.0ฉันจะถือว่าคุณใช้เวอร์ชัน 2.0 ปัจจุบัน ในกรณีนี้สร้างที่เก็บ git ทุกที่ที่คุณต้องการและใส่ทุกอย่างลงไปสิ่งที่คุณต้องการพัฒนาให้เป็นอิสระจาก symfony ใน symfony- โปรเจ็กต์อัพเดต-fileของคุณตามที่ได้depsกล่าวไว้ที่นี่symfony.com/doc/current/cookbook/workflow/ ......จากนั้นเพียงสร้างแอปพลิเคชันบันเดิลหนึ่งชุดphp app/console generate:bundleขึ้นไป
KingCrunch

11

การกระจาย symfony ปกติสามารถทำงานได้โดยไม่ต้องมีบันเดิลพิเศษ (แอปพลิเคชัน) ใด ๆ ทั้งนี้ขึ้นอยู่กับจำนวนฟังก์ชันที่คุณต้องการใช้จากเฟรมเวิร์กสแต็กแบบเต็ม

ตัวอย่างเช่นตัวควบคุมของคุณสามารถเรียกได้ที่สามารถวางไว้ที่ใดก็ได้ในโครงสร้างโครงการของคุณทันทีที่พวกเขาจะโหลดอัตโนมัติ

ในไฟล์การกำหนดเส้นทางคุณสามารถใช้:

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

มันสามารถเป็นวัตถุ php แบบเก่า ๆ ที่ผูกกับกรอบเท่านั้นโดยที่มันต้องส่งคืนSymfony\Component\HttpFoundation\Responseวัตถุ

เทมเพลตกิ่งของคุณ (หรืออื่น ๆ ) สามารถใส่ได้app/Resources/views/template.html.twigและสามารถแสดงผลโดยใช้::template.html.twigชื่อโลจิคัล

บริการ DI ทั้งหมดสามารถกำหนดได้ใน app / config / config.yml (หรือนำเข้าจากapp/config/services.ymlตัวอย่างและคลาสบริการทั้งหมดสามารถเป็นวัตถุ php แบบธรรมดาใด ๆ ได้เช่นกันไม่ผูกติดกับกรอบเลย

ทั้งหมดนี้ให้ไว้โดยปริยายโดยเฟรมเวิร์กสแต็กเต็มของ symfony

ที่คุณจะมีปัญหาคือเมื่อคุณจะต้องการใช้ไฟล์การแปล (เช่น xliff) เพราะพวกเขาจะค้นพบผ่านการรวมกลุ่มเท่านั้น

การกระจายแสงแบบ Symfonyมีจุดมุ่งหมายเพื่อแก้ปัญหาแบบนี้โดยการค้นพบทุกสิ่งที่มักจะถูกค้นพบผ่านชุดข้อมูลเท่านั้น


5

คุณสามารถใช้KnpRadBundleซึ่งพยายามทำให้โครงสร้างโครงการง่ายขึ้น

อีกวิธีหนึ่งคือใช้src/Company/Bundle/FrontendBundleตัวอย่างสำหรับบันเดิลและsrc/Company/Stuff/Class.phpคลาสที่เป็นอิสระจาก symfony และสามารถนำกลับมาใช้ใหม่นอกกรอบ


แต่จากนั้นฉันจะเชื่อมต่อแอปพลิเคชันกับ KnpRadBundle ... ไม่มีวิธีการใดที่ง่ายกว่าในเรื่องนี้หรือไม่?
Daniel Ribeiro

1
ชิ้นส่วนที่ขึ้นอยู่กับ symfony (ตัวควบคุมรุ่นแม่แบบ ฯลฯ ... ) จะเชื่อมต่อกับ symfony เสมอเนื่องจากคุณกำลังใช้งานอยู่ (การขยายคลาสใช้ตัวช่วย ฯลฯ ) คลาสที่ทำงานคนเดียวจะอยู่ในเนมสเปซของ บริษัท และคุณสามารถโหลดได้โดยใช้คอนเทนเนอร์การพึ่งพา คลาสเหล่านี้สามารถเป็นกรอบงานได้อย่างอิสระ
miguel_ibero

1
สิ่งคือแนวคิดของการBundleไปสู่การแบ่งปันสาธารณะโดยตรง เมื่อฉันเขียนแอปพลิเคชันบางอย่างฉันไม่ต้องการแบ่งปันรหัสยกเว้นส่วนที่ฉันตั้งใจสร้างเป็นโมดูลที่ขับเคลื่อนโดยชุมชน ฉันผิดหรือเปล่า?
Daniel Ribeiro

คุณไม่ต้องแชร์บันเดิล คิดว่ามัดเป็นกลุ่มของชั้นเรียนที่มีการกำหนดค่าบางอย่าง ในแต่ละโครงการคุณสามารถมีกลุ่มที่แตกต่างกัน
miguel_ibero

คุณควรอ่านหนังสือ symfony
miguel_ibero

5

เนื่องจากผ่านไปแล้ว 5 ปีนี่คือบทความอีกสองสามรายการเกี่ยวกับ Symfony Bundles

  1. การรวมกลุ่มใน Symfony คืออะไร โดย Iltar van der Berg

TLDR:

คุณต้องการชุดข้อมูลหลายชุดในใบสมัครของคุณโดยตรงหรือไม่? ส่วนใหญ่ไม่น่า คุณควรเขียน AppBundle เพื่อป้องกันปาเก็ตตี้ที่ต้องพึ่งพา คุณสามารถปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดและมันจะทำงานได้ดี

  1. Symfony: วิธีการมัดโดย Toni Uebernickel

TLDR:

สร้างบันเดิลเพียงหนึ่งชุดที่เรียกว่า AppBundle สำหรับตรรกะแอปพลิเคชันของคุณ หนึ่ง AppBundle - แต่โปรดอย่าใส่ตรรกะแอปพลิเคชันของคุณไว้ในนั้น!


-2

เฟรมเวิร์ก Symfony นั้นดีมากสำหรับการเปิดใช้งานการพิสูจน์แนวคิดอย่างรวดเร็วและโค้ดทั้งหมดสามารถป้อนภายในแอปพลิเคชันบันเดิลเริ่มต้นใน src /

ในชุดนี้คุณสามารถจัดโครงสร้างโค้ดตามที่คุณต้องการ

หลังจากหากคุณต้องการใช้เทคโนโลยีอื่นเพื่อพัฒนา POC ของคุณคุณสามารถแปลได้อย่างง่ายดายเพราะคุณไม่ได้จัดโครงสร้างรหัสทั้งหมดของคุณในการรวมกลุ่ม

สำหรับแนวคิดทั้งหมดคุณไม่ได้คลั่งไคล้สิ่งนี้ มัดเป็นสิ่งที่ดี แต่มัดทุกอย่างและทุกวันไม่ดี

บางทีคุณอาจใช้ Silex (เฟรมเวิร์กไมโคร Symfony) เพื่อพัฒนา Proof of Concept ของคุณเพื่อลดผลกระทบของกลุ่มบุคคลที่สาม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.