Organizing Your Business Logic
Warning: You are browsing the documentation for Symfony 3.x, which is no longer maintained.
Read the updated version of this page for Symfony 7.1 (the current stable version).
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 everything inside the AppBundle. Inside here, you can create whatever directories you want to organize things:
1 2 3 4 5 6 7 8 9 10
symfony-project/
├─ app/
├─ src/
│ └─ AppBundle/
│ └─ Utils/
│ └─ MyClass.php
├─ tests/
├─ var/
├─ vendor/
└─ web/
Storing Classes Outside of the Bundle?
But there's no technical reason for putting business logic inside of a bundle.
If you like, you can create your own namespace inside the src/
directory
and put things there:
1 2 3 4 5 6 7 8 9 10 11
symfony-project/
├─ app/
├─ src/
│ ├─ Acme/
│ │ └─ Utils/
│ │ └─ MyClass.php
│ └─ AppBundle/
├─ tests/
├─ var/
├─ vendor/
└─ web/
Tip
The recommended approach of using the AppBundle/
directory is for
simplicity. If you're advanced enough to know what needs to live in
a bundle and what can live outside of one, then feel free to do that.
Services: Naming and Format
The blog application needs a utility that can transform a post title (e.g. "Hello World") into a slug (e.g. "hello-world"). The slug will be used as part of the post URL.
Let's create a new Slugger
class inside src/AppBundle/Utils/
and
add the following slugify()
method:
1 2 3 4 5 6 7 8 9 10 11 12
// src/AppBundle/Utils/Slugger.php
namespace AppBundle\Utils;
class Slugger
{
public function slugify($string)
{
return preg_replace(
'/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string)))
);
}
}
Next, define a new service for that class.
1 2 3 4 5 6 7
# app/config/services.yml
services:
# ...
# use the fully-qualified class name as the service id
AppBundle\Utils\Slugger:
public: false
Note
If you're using the default services.yml configuration, the class is auto-registered as a service.
Traditionally, the naming convention for a service was a short, but unique
snake case key - e.g. app.utils.slugger
. But for most services, you should now
use the class name.
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 controller class, such as the
AdminController
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
use AppBundle\Utils\Slugger;
public function createAction(Request $request, Slugger $slugger)
{
// ...
// you can also fetch a public service like this
// but fetching services in this way is not considered a best practice
// $slugger = $this->get('app.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.yml 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.
Service Format: YAML
In the previous section, YAML was used to define the service.
Best Practice
Use the YAML format to define your own services.
This is controversial, and in our experience, YAML and XML usage is evenly distributed among developers, with a slight preference towards YAML. Both formats have the same performance, so this is ultimately a matter of personal taste.
We recommend YAML because it's friendly to newcomers and concise, but you can use whatever format you like.
Service: No Class Parameter
You may have noticed that the previous service definition doesn't configure the class namespace as a parameter:
1 2 3 4 5 6 7 8 9
# app/config/services.yml
# service definition with class namespace as parameter
parameters:
slugger.class: AppBundle\Utils\Slugger
services:
app.slugger:
class: '%slugger.class%'
This practice is cumbersome and completely unnecessary for your own services.
Best Practice
Don't define parameters for the classes of your services.
This practice was wrongly adopted from third-party bundles. When Symfony introduced its service container, some developers used this technique to allow overriding services. However, overriding a service by just changing its class name is a very rare use case because, frequently, the new service has different constructor arguments.
Using a Persistence Layer
Symfony is an HTTP framework that only cares about generating an HTTP response for each HTTP request. That's why Symfony doesn't provide a way to talk to a persistence layer (e.g. database, external API). You can choose whatever library or strategy you want for this.
In practice, many Symfony applications rely on the independent Doctrine project to define their model using entities and repositories.
Doctrine support is not enabled by default in Symfony. So to use Doctrine as shown in the examples below you will need to install Doctrine ORM support by executing the following command:
1
$ composer require symfony/orm-pack
Just like with business logic, we recommend storing Doctrine entities in the AppBundle.
The three entities defined by our sample blog application are a good example:
1 2 3 4 5 6 7 8
symfony-project/
├─ ...
└─ src/
└─ AppBundle/
└─ Entity/
├─ Comment.php
├─ Post.php
└─ User.php
Tip
If you're more advanced, you can store them under your own namespace in src/
.
Doctrine Mapping Information
Doctrine entities are plain PHP objects that you store in some "database". Doctrine only knows about your entities through the mapping metadata configured for your model classes. Doctrine supports four metadata formats: YAML, XML, PHP and annotations.
Best Practice
Use annotations to define the mapping information of the Doctrine entities.
Annotations are by far the most convenient and agile way of setting up and looking for mapping information:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Post
{
const NUMBER_OF_ITEMS = 10;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string")
*/
private $title;
/**
* @ORM\Column(type="string")
*/
private $slug;
/**
* @ORM\Column(type="text")
*/
private $content;
/**
* @ORM\Column(type="string")
*/
private $authorEmail;
/**
* @ORM\Column(type="datetime")
*/
private $publishedAt;
/**
* @ORM\OneToMany(
* targetEntity="App\Entity\Comment",
* mappedBy="post",
* orphanRemoval=true
* )
* @ORM\OrderBy({"publishedAt"="ASC"})
*/
private $comments;
public function __construct()
{
$this->publishedAt = new \DateTime();
$this->comments = new ArrayCollection();
}
// getters and setters ...
}
All formats have the same performance, so this is once again ultimately a matter of taste.
Data Fixtures
As fixtures support is not enabled by default in Symfony, you should execute the following command to install the Doctrine fixtures bundle:
1
$ composer require --dev doctrine/doctrine-fixtures-bundle
Then, enable the bundle in AppKernel.php
, but only for the dev
and
test
environments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = [
// ...
];
if (in_array($this->getEnvironment(), ['dev', 'test'])) {
// ...
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
}
return $bundles;
}
// ...
}
We recommend creating just one fixture class for simplicity, though you're welcome to have more if that class gets quite large.
Assuming you have at least one fixtures class and that the database access is configured properly, you can load your fixtures by executing the following command:
1 2 3 4 5
$ php bin/console doctrine:fixtures:load
Careful, database will be purged. Do you want to continue Y/N ? Y
> purging database
> loading AppBundle\DataFixtures\ORM\LoadFixtures
Coding Standards
The Symfony source code follows the PSR-1 and PSR-2 coding standards that were defined by the PHP community. You can learn more about the Symfony Coding standards and even use the PHP-CS-Fixer, which is a command-line utility that can fix the coding standards of an entire codebase in a matter of seconds.
Next: Controllers