Skip to content

Instantly share code, notes, and snippets.

@cronv
Created February 27, 2025 09:15
Show Gist options
  • Select an option

  • Save cronv/f5c917ed45c619d22848986089d0e19a to your computer and use it in GitHub Desktop.

Select an option

Save cronv/f5c917ed45c619d22848986089d0e19a to your computer and use it in GitHub Desktop.
Symfony User Registration Refactoring

Задача: Рефакторинг существующего кода

Контекст: У вас есть часть приложения на Symfony, отвечающая за управление пользователями. В рамках этой задачи вам необходимо будет отредактировать функцию, которая отвечает за регистрацию пользователей, чтобы сделать её более читаемой и эффективной.

Исходный код:

// src/Controller/UserController.php

namespace App\Controller;

use App\Entity\User;
use App\Form\UserType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class UserController extends AbstractController
{
    /**
     * @Route("/register", name="register")
     */
    public function register(Request $request, EntityManagerInterface $entityManager): Response
    {
        $user = new User();
        $form = $this->createForm(UserType::class, $user);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $hashedPassword = password_hash($user->getPassword(), PASSWORD_BCRYPT);
            $user->setPassword($hashedPassword);
            $entityManager->persist($user);
            $entityManager->flush();

            return $this->redirectToRoute('app_home');
        }

        return $this->render('user/register.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

Задание:

  • Убедитесь, что код соответствует стандартам PSR.
  • Сделайте функции более понятными и структурированными. Если необходимо, выделите в отдельные методы.

Анализ кода на соответствие стандартам и принципам проектирования

1. PSR (PHP Standards Recommendations)

UserController

  • PSR-1: Используются правильные пространства имен и имена классов.
  • PSR-2: Код отформатирован с правильными отступами и пробелами.
  • PSR-4: Структура каталогов соответствует пространствам имен.

UserRegistrationDTO

  • PSR-1: Соответствие стандартам именования и использования аннотаций.
  • PSR-2: Код отформатирован правильно, соблюдаются отступы и пробелы.

UserRegistrationService

  • PSR-1: Используются правильные пространства имен и имена классов.
  • PSR-2: Код отформатирован с правильными отступами и пробелами.

UserType (Форма)

  • PSR-1: Соответствие стандартам именования и использования аннотаций.
  • PSR-2: Код отформатирован правильно, соблюдаются отступы и пробелы.

2. SOLID

UserRegistrationDTO

  • S - Single Responsibility Principle (SRP):
    • Отвечает только за хранение данных пользователя и валидацию.

UserRegistrationService

  • S - Single Responsibility Principle (SRP):
    • Отвечает только за логику регистрации пользователя.
  • O - Open/Closed Principle (OCP):
    • Легко расширяем для добавления новых методов валидации или логики регистрации.
  • D - Dependency Inversion Principle (DIP):
    • Зависит от интерфейса ValidatorInterface и EntityManagerInterface, а не от конкретных реализаций.

UserController

  • S - Single Responsibility Principle (SRP):
    • Отвечает только за обработку запросов, связанных с пользователями.

UserType (Форма)

  • S - Single Responsibility Principle (SRP):
    • Отвечает только за создание формы для регистрации пользователя.

3. DRY (Don't Repeat Yourself)

  • Весь код для валидации и обработки регистрации вынесен в User RegistrationService, что предотвращает дублирование логики в контроллере.
  • DTO User RegistrationDTO используется для передачи данных, что уменьшает количество повторяющегося кода.

4. KISS (Keep It Simple, Stupid)

  • Код написан просто и понятно. Например, методы имеют четкие названия, и их логика легко воспринимается.
  • Логика регистрации пользователя разбита на небольшие, понятные методы.

5. POLA (Principle of Least Astonishment)

  • Поведение методов интуитивно понятно. Например, методы registerUser и handleRequest делают именно то, что от них ожидается.
  • Использование DTO для передачи данных также делает код более предсказуемым.

6. Fail Fast

  • В User RegistrationService используется валидация данных с помощью ValidatorInterface. Если данные не валидны, возвращается массив ошибок, что позволяет быстро выявить и обработать проблемы.
  • Контроллер обрабатывает ошибки и отображает их пользователю, что также соответствует принципу быстрого выявления ошибок.

7. Separation of Concerns (Разделение ответственности)

  • Код разделен на различные слои: контроллер, сервис и DTO. Каждый класс отвечает за свою область, что улучшает поддержку и тестируемость.

8. Encapsulation (Инкапсуляция)

  • В User RegistrationDTO используются приватные свойства с публичными методами доступа, что скрывает внутреннюю реализацию и защищает данные.

9. Interface Segregation Principle (ISP)

  • В User RegistrationService используется ValidatorInterface, что позволяет не зависеть от методов, которые не используются.

10. Composition over Inheritance

  • Вместо создания сложных иерархий классов используется композиция через сервисы и DTO, что делает код более гибким и простым.

DTO

DTO
// src/DTO/UserRegistrationDTO.php

namespace App\DTO;

use Symfony\Component\Validator\Constraints as Assert;

class UserRegistrationDTO
{
    #[Assert\NotBlank]
    #[Assert\Length(min: 6)]
    private string $username;

    #[Assert\NotBlank]
    #[Assert\Length(min: 8)]
    private string $password;

    public function getUsername(): string
    {
        return $this->username;
    }

    public function setUsername(string $username): self
    {
        $this->username = $username;
        return $this;
    }

    public function getPassword(): string
    {
        return $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;
        return $this;
    }
}

Service

Service
// src/Service/UserRegistrationService.php

namespace App\Service;

use App\DTO\UserRegistrationDTO;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class UserRegistrationService
{
    private EntityManagerInterface $entityManager;
    private ValidatorInterface $validator;

    public function __construct(EntityManagerInterface $entityManager, ValidatorInterface $validator)
    {
        $this->entityManager = $entityManager;
        $this->validator = $validator;
    }

    public function registerUser(UserRegistrationDTO $userDTO): array
    {
        $errors = $this->validator->validate($userDTO);
        if (count($errors) > 0) {
            return (array) $errors;
        }

        $user = new User();
        $user->setUsername($userDTO->getUsername());
        $hashedPassword = password_hash($userDTO->getPassword(), PASSWORD_BCRYPT);
        $user->setPassword($hashedPassword);

        $this->entityManager->persist($user);
        $this->entityManager->flush();

        return [];
    }
}

Controller

Controller
// src/Controller/UserController.php

namespace App\Controller;

use App\DTO\UserRegistrationDTO;
use App\Form\UserType;
use App\Service\UserRegistrationService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class UserController extends AbstractController
{
    #[Route('/register', name: 'register')]
    public function register(Request $request, UserRegistrationService $registrationService): Response
    {
        $userDTO = new UserRegistrationDTO();
        $form = $this->createForm(UserType::class, $userDTO);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            if ($errors = $registrationService->registerUser ($userDTO)) {
                return $this->render('user/register.html.twig', [
                    'form' => $form->createView(),
                    'errors' => $errors,
                ]);
            }

            return $this->redirectToRoute('app_home');
        }

        return $this->render('user/register.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

Form

Form
// src/Form/UserType.php

namespace App\Form;

use App\DTO\UserRegistrationDTO;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('username', TextType::class)
            ->add('password', PasswordType::class);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => UserRegistrationDTO::class,
        ]);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment