Mastering Symfony
上QQ阅读APP看书,第一时间看更新

Anatomy of a bundle

When you install Symfony (via the default installer), it comes with a very basic controller and template. That is why we can see the default Welcome! screen by visiting the following URL:

http://localhost:8000

The general folder structure for a Symfony project is as follows:

└── mava
    ├── app
    ├── bin
    ├── src
    ├── tests
    ├── var
    ├── vendor
    └── web

The folders that we are interested at the moment are src/ and app/. They contain the code and template for the Welcome! screen. In the src/ folder, we have a bundle called AppBundle with the following basic structure:

src/
└── AppBundle
    ├── AppBundle.php
    └── Controller
        └── DefaultController.php

The default controller is where the so-called handle() method passes the request and expects a response. Let's have a look in this controller:

// mava/src/AppBundle/Controller/DefaultController.php
class DefaultController extends Controller
{
  /**
   * @Route("/", name="homepage")
   */
  public function indexAction(Request $request)
  {
    return $this->render('default/index.html.twig', [
      'base_dir' => realpath($this->container-> getParameter('kernel.root_dir').'/..'),
    ]);
  }
}

Behind the scene, the handle() method asks the router to find a matching route for the home page request. The router looks at the available routes stack, finds the one defined for the indexAction() method, and passes the request to it.

If you are wondering what the route for indexAction() is, look at the @Route() annotation in the comments before the method body. This annotation defines the route for the action. You can prove it by looking at the available routes in the command line:

$ bin/console debug:router
 -------------------------- -------- -------- ------ ----------
------
 Name Method Scheme Host Path 
 -------------------------- -------- -------- ------ ----------------
 _wdt ANY ANY ANY /_wdt/{token} 
 _profiler_home ANY ANY ANY /_profiler/ 
...
 homepage ANY ANY ANY / 
 -------------------------- -------- -------- ------ ----------------

As you can see, the last line shows the route name and path for the home page.

Let's go back to controller indexAction() and see what happens to the request that we just received. We have only one simple render() function, which basically assembles a string for the project base URL and passes it to a template stored in the Resources/ directory to be rendered.

The template engine gets the base_dir parameter, uses it in the available template, generates an HTML page, and returns it as the response.

This default bundle is minimized to the very basic structure and is for demonstration purposes only. Let's create a new bundle and see how it looks.

Generating a new bundle

There are two ways to create a new bundle. You can do it manually by creating classes or YAML files and organizing them in folders created manually in the src/ folder of your project (and use your IDE's code generation feature to fill the blanks along the way).

You can also use the Symfony's interactive console to do the job for you. While you are at the root of the project, create a new bundle called MyBundle via the following command:

$ bin/console generate:bundle

Set the name and accept all default answers for the next questions. At the end, you will see a message confirming that the bundle was generated successfully:

> Generating a sample bundle skeleton into src/MyBundle OK!
> Checking that the bundle is autoloaded: OK
> Enabling the bundle inside app/AppKernel.php: OK
> Importing the bundle's routes from the app/config/routing.yml file: OK
> Importing the bundle's services.yml from the app/config/config.yml file: OK

Let's see what each of these lines mean. The first line confirms that we have the folder structure for the new bundle. Check the src/ directory and you will see this here:

src/
├── AppBundle
│   ├── AppBundle.php
│   └── Controller
│       └── DefaultController.php
└── MyBundle
    ├── Controller
    │   └── DefaultController.php
    ├── MyBundle.php
    ├── Resources
    │   ├── config
    │   │   └── services.yml
    │   └── views
    │       └── Default
    │           └── index.html.twig
    └── Tests
        └── Controller
            └── DefaultControllerTest.php

Compared to the default AppBundle, we have a few more files and folders in the generated bundle. We will get to that in a minute.

Now check the AppKernel.php file and, as you can see, the bundle is registered in our project:

// mava/app/AppKernel.php
class AppKernel extends Kernel
{
  public function registerBundles()
  {
    $bundles = [
      // . . .
      new AppBundle\AppBundle(),
      new MyBundle\MyBundle(),
    ];
  }
  // . . .
}

In the app/config/routing.yml file, we can see settings for the new bundle. We chose the default option while generating the bundle. This means that routes will be created from the controller action methods and their @Route() annotations:

# app/config/routing.yml
my:
  resource: "@MyBundle/Controller/"
  type:     annotation
  prefix:   /

Finally, in app/config/config.yml, the (future) services of our generated bundle are imported:

# app/config/config.yml
imports:
  - { resource: parameters.yml }
  - { resource: security.yml }
  - { resource: services.yml }
  - { resource: "@MyBundle/Resources/config/services.yml" }

This means that we are all set and good to start coding. To prove this, open the DefaultController for our new bundle and change @Route() as follows:

class DefaultController extends Controller
{
  /**
   * @Route("/my", name="mypage")
   */
  public function indexAction()
  {
    return $this->render('MyBundle:Default:index.html.twig');
  }
}

Now, we can find the new route with the debug:router console command and we can see it in action by visiting http://localhost:8000/my in the browser.

Best practices

The question here is why do we need a new bundle? Couldn't we modify the current AppBundle instead? Yes, we could. Actually, it is totally up to you how you organize your code. Symfony won't complain about creating a new folder at the route of your project, naming it whatever you like, and organizing your code in a couple of subfolders over there. As long as you register your bundle in AppKernel.php and update the routing and config file with proper references, everything is fine.

Before doing this, ask yourself: does this contribute to the easier maintenance of your project? Think about the developers who are going to take over and maintain the code after you. We can have one bundle, call it AppBundle, and put all the application logic in its folders, or we can have as many bundles as we wish and create each piece of application logic in one of them (UserBundle, ProjectBundle, TaskBundle, and so on).

There is absolutely no right or wrong way and the way you organize your code has no effect on Symfony's performance. However, what I've learned from my past experiences is to keep things as simple as possible. Basically, from Symfony 3.x onward, I have decided to use the default Symfony application structure and AppBundle as a base for everything.

I create a new structure only if I need to reuse my code in some other projects (that is, a third-party bundle) or my project requires some specific configurations that cannot be met by default Symfony settings.

To help you make decisions about what to do and how to write and organize your code, Symfony has a best practices document that you can find here:

http://symfony.com/doc/current/best_practices/index.html

What I will do during this book is mention the related best practices for each topic in an information box where we need to make a decision about coding or organizing style.

Note

To keep things simple and increase the project maintainability, keep your code in the AppBundle.