Организация вашей бизнес-логики

Организация вашей бизнес-логики

В компьютерном ПО, бизнес-логика или логика домена - это "часть программы, которая шифрует правила бизнеса реального мира, которые определяют то, как могут быть созданы, отображены, сохранены и изменены данные (см. полное определение).

В приложениях Symfony, бизнес-логика - это весь пользовательский код, который вы пишете для вашего приложения, который не характерен исключительно для фреймворка (например, маршрутизация и контроллеры). Классы домена, сущности Doctrine и обычные PHP-классы, которые используются в качестве сервисов, являются хорошими примерами бизнес-логики.

Для большинства проектов вам стоит хранить всё внутри пакета AppBundle. Там, вы можете создать любые желаемые вами каталоги для систематизации вещей:

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

Хранение классов вне пакета?

Однако не существует технической причины хранения бизнес-логики в пакете. Если вы хотите, вы можете создать ваше собственное пространство имён в каталоге src/, и поместить всё туда:

 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

Рекомендованный подход к использованию каталога AppBundle/ заключается в простоте. Если вы достаточно продвинуты, чтобы знать, что должно жить в пакете, а что может жить вне него, то действуйте по своему усмотрению.

Сервисы: Именование и формат

Приложение блога требует утидиты, которая может преобразовывать заголовок поста (например, "Привет, мир") в строку (например, "привет-мир"). Строка будет использована в качестве части URL поста.

Давайте создадим новый класс Slugger внутри src/AppBundle/Utils/ и добавим следующий метод slugify():

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

Далее, определите новый сервис для этого класса.

1
2
3
4
5
6
7
# app/config/services.yml
services:
    # ...

    # используйте полное имя класса в качестве id сервиса
    AppBundle\Utils\Slugger:
        public: false

Note

Если вы используете конфигурацию services.yml по умолчанию, то класс регистрируется в качестве сервиса автоматически.

Традиционно, соглашение об именовании сервиса заключалось в коротком, но уникальном ключе "змеиной кожи" - например, app.utils.slugger. Но для большинства сервисов, вам стоит использовать имя класса.

Best Practice

Id сервисов вашего приложения должны совпадать с их именем класса, кроме сдучаев, когда у вас сконфигурировано несколько сервисов для одного класса (в этом случае, используйте id "змеиной кожи".

Теперь вы можете использовать пользовательский слаггер в любом классе контроллера, как 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)
{
    // ...

    // вы также можете вызвать публичный сервис
    // но вызов сервисов таким образом не считается лучшей практикой
    // $slugger = $this->get('app.slugger');

    if ($form->isSubmitted() && $form->isValid()) {
        $slug = $this->get('app.slugger')->slugify($post->getTitle());
        $post->setSlug($slug);

        // ...
    }
}

Сервисы также могут быть публичными или приватными. Если вы используете конфигурацию services.yml по умолчанию, то все сервисы приватные по умолчанию.

Best Practice

Сервисы должны быть private всегда, когда это возможно. Это убережёт вас от доступа к сервису через $container->get(). Вместо этого, вам нужно будет использовать внедрение зависимости.

Формат сервиса: YAML

В предыдущем разделе, YAML использовался для определения сервиса.

Best Practice

Используйте формат YAML, чтобы определять ваши собственные сервисы.

Это спорно, и по нашему опыту, использование YAML и XML распределяется среди разработчиков одинаково, с небольшим преимуществом в пользу YAML. Оба формата имеют одинаковую производительность, так что обычно это вопрос личного предпочтения.

Мы рекомендуем YAML, так как он дружелюбен по отношениюк новичкам и компактен. Вы, конечно, можете использовать любой формат, который хотите.

Сервис: Отсутствие параметра класса

Вы могли заметить, что предыдущее определение сервиса не конфигурирует пространство имён класс в качестве параметра:

1
2
3
4
5
6
7
8
9
# app/config/services.yml

# определение сервиса с пространством имён в качестве параметра
parameters:
    slugger.class: AppBundle\Utils\Slugger

services:
    app.slugger:
        class: '%slugger.class%'

Эта практика утомительна и абсолютно не нужная для ваших собственных сервисов.

Best Practice

Не определяйте параметры для классов ваших сервисов.

Эта практика была ошибочно принята из сторонних пакетов. Когда Symfony представила свой сервис-контейнер, некоторые разработчики использовали эту технику, чтобы с лёгкостью разрешить переопределение сервисов. Однако, переопределение сервисов просто путём изменения имени класса - очень редкий случай исползования, так как зачастую новый сервис имеет другие аргументы конструктора.

Использование уровня хранения

Symfony - это HTTP-фреймворк, который занят только генерированием HTTP-ответа для каждого HTTP-запроса. Поэтому Symfony не предоставляет способ общения с уровнем хранения (например, БД, внешним API). Вы можете выбрать любую билиотеку или стратегию, которую хотите.

На практике, многие приложения Symfony полагаются на независимый проект Doctrine, чтобы определять их модель, используя сущности и хранилища. Так же, как и с бизнес- логикой, мы рекомендуем хранить сущности Doctrine в AppBundle.

Три сущности, определённые нашим пробным приложением блога, являются хорошим примером:

1
2
3
4
5
6
7
8
symfony-project/
├─ ...
└─ src/
   └─ AppBundle/
      └─ Entity/
         ├─ Comment.php
         ├─ Post.php
         └─ User.php

Tip

Если вы более продвинуты, вы, конечно же, можете хранить их в вашем собственном пространстве имён в src/.

Информация маршрутизации Doctrine

Сущности Doctrine являются простыми PHP-объектами, который вы храните в некоторой "БД". Doctrine знает о ваших сущностях только через метаданные маршрутизации, сконфигурированные для ваших классов моделей. Doctrine поддерживает четыре формата метаданных: YAML, XML, PHP и аннотации.

Best Practice

Используйте аннотации, чтобы определить информацию маршрутизации сущностей Doctrine.

Аннотации являются несравненно наиболее удобным и гибким методом настройки и поиска информации о маоршрутизации:

 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\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\Entity
 */
class Post
{
    const NUM_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="Comment",
     *      mappedBy="post",
     *      orphanRemoval=true
     * )
     * @ORM\OrderBy({"publishedAt" = "ASC"})
     */
    private $comments;

    public function __construct()
    {
        $this->publishedAt = new \DateTime();
        $this->comments = new ArrayCollection();
    }

    // геттеры и сеттеры ...
}

Все форматы имеют одинаковую производительность, так что это опять-таки чисто вопрос предпочтений.

Фиксаторы данных

Так как поддержка фиксаторов не включена в Symfony по умолчанию, вам нужно выполнить следующую команду, чтобы установить пакет фиксаторов Doctrine:

1
$ composer require "doctrine/doctrine-fixtures-bundle"

Далее, включите пакет в AppKernel.php, но только для окружений dev и test:

 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 = array(
            // ...
        );

        if (in_array($this->getEnvironment(), array('dev', 'test'))) {
            // ...
            $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
        }

        return $bundles;
    }

    // ...
}

Мы рекомендуем создавать только один класс фиксаторов лля простоты, хотя вы всегда можете создать больше, если этот класс станет достаточно большим.

Предполагая, что у вас есть как минимум один класс фиксторов, и что доступ к БД сконфигурирован правильно, вы можете загрузить ваши фиксаторы, выполнив следующую команду:

1
2
3
4
5
$ php bin/console doctrine:fixtures:load

Осторожно, БД будет очищена. Вы хотите продолжить Да/Нет? Да
  > purging database
  > loading AppBundle\DataFixtures\ORM\LoadFixtures

Стандарты кодировки

Исходный код Symfony следует стандартам кодировки PSR-1 и PSR-2, которые были определены PHP-сообществом. Вы можете узнать больше о стандартах кодировки Symfony и даже использовать PHP-CS-Fixer - утилиту комнадной строки, которая может исправлять стандарты кодировки целой кодовой базы за считанные секунды.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.