Organizing Your Business Logic

In computer software, business logic or domain logic is "the part of the program that encodes the real-world business rules that determine how data can be created, displayed, stored, and changed" (read full definition).

In Symfony applications, business logic is all the custom code you write for your app that's not specific to the framework (e.g. routing and controllers). Domain classes, Doctrine entities and regular PHP classes that are used as services are good examples of business logic.

For most projects, you should store all your code inside the src/ directory. Inside here, you can create whatever directories you want to organize things:

1
2
3
4
5
6
7
8
9
symfony-project/
├─ config/
├─ public/
├─ src/
│  └─ Utils/
│     └─ MyClass.php
├─ tests/
├─ var/
└─ vendor/

Services: Naming and Configuration

Best Practice

Use autowiring to automate the configuration of application services.

Service autowiring is a feature provided by Symfony's Service Container to manage services with minimal configuration. It reads the type-hints on your constructor (or other methods) and automatically passes the correct services to each method. It can also add service tags to the services needing them, such as Twig extensions, event subscribers, etc.

The blog application needs a utility that can transform a post title (e.g. "Hello World") into a slug (e.g. "hello-world") to include it as part of the post URL. Let's create a new Slugger class inside src/Utils/:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// src/Utils/Slugger.php
namespace App\Utils;

class Slugger
{
    public function slugify(string $value): string
    {
        // ...
    }
}

If you're using the default services.yaml configuration, this class is auto-registered as a service with the ID App\Utils\Slugger (to prevent against typos, import the class and write Slugger::class in your code).

Best Practice

The id of your application's services should be equal to their class name, except when you have multiple services configured for the same class (in that case, use a snake case id).

Now you can use the custom slugger in any other service or controller class, such as the AdminController:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use App\Utils\Slugger;

public function create(Request $request, Slugger $slugger)
{
    // ...

    if ($form->isSubmitted() && $form->isValid()) {
        $slug = $slugger->slugify($post->getTitle());
        $post->setSlug($slug);

        // ...
    }
}

Services can also be public or private. If you use the default services.yaml configuration, all services are private by default.

Best Practice

Services should be private whenever possible. This will prevent you from accessing that service via $container->get(). Instead, you will need to use dependency injection.