очень вдохновляющая фотография для статьи.
очень вдохновляющая фотография для статьи.

Эта статья — подробный разбор тестового задания на позицию PHP-разработчика, которое мне пришлось выполнить в краткие сроки из-за приближающей сессии. Я расскажу, с какими трудностями столкнулся, будучи студентом и новичком в веб-разработке, как построил архитектуру проекта «Школьные консультации» и что из этого вышло. Если вам интересен взгляд «с первоначального этапа» на классическую задачу или вы хотите узнать, чем всё кончилось, или у вас был похожий опыт (о котором вы хотели рассказать), то добро пожаловать.

План статьи:

  1. Введение (о чём статья, мотивация и предыстория).

  2. Основная часть (как проходила работа и проект).

  3. Итог (выводы и личные размышления).

1. Введение: от шутки до дедлайна

Главный вопрос, который может возникнуть у читателя: «При чём тут админ и студент? И о чём статья?». Начну сначала.

Эта статья — рассказ о том, как я прошёл мини-квест на пути к новой роли внутри компании. Как я писал в прошлой статье, в конце ноября мне, уже работающему в компании на другой должности, неожиданно предложили взять на себя часть обязанностей веб-разработчика на PHP. Почему «часть»? Потому что совмещать планировалось с текущими задачами.

Эта картинка объясняет, почему у меня как у студента были проблемы в декабре.
Эта картинка объясняет, почему у меня как у студента были проблемы в декабре.

Сначала это прозвучало как шутка, но в начале декабря мне прислали полноценное ТЗ с дедлайном в одну неделю. И вот здесь начался настоящий вызов и проблема. Я студент, а декабрь — это время закрытия учебных хвостов и подготовки к зачётам. В итоге на полноценную работу над проектом у меня было всего два-три дня. Качество кода, честно говоря, далеко от идеала, но для первого серьёзного проекта на языке, с которым я ранее почти не работал, результат, думаю, получился достойным

Мотивация у меня простая: я люблю изучать новое и создавать. Это не только прокачивает мои hard skills, но и учит тому, что я знаю не так много и не готов общаться с профессионалами на их языке. Кто-то скажет, что это несерьёзно, но я уверен: именно такие порывы обычно и побуждают делать что-то новое. Без того первого «шуточного» предложения от начальства не было бы и моей прошлой статьи на Хабре, и этого нового опыта (заранее напишу, пока не было обратки, поэтому было бы приятно её получить от вас).


2. Основная часть

2.1 Постановка задачи: начнём с фундамента

Прежде чем обсуждать решения, важно понять исходные данные. Нельзя строить дом без чертежа, а сервис без технического задания. Вот точный текст ТЗ, с которого всё началось.

Тестовое задание

Описание:
Предположим, что приёмная комиссия РТУ МИРЭА проводит индивидуальные и групповые консультации для абитуриентов (очно и онлайн).
Нужно сделать небольшой сервис, который:

  • хранит список консультаций,

  • позволяет абитуриентам записываться,

  • контролирует количество мест,

  • предоставляет API внешним сервисам.

Что нужно сделать:

  1. Описание сущностей:
    Нужно реализовать как минимум две сущности в БД:

    Consultation (консультация).

    Registration (запись абитуриента на консультацию).

  2. Бизнес-ограничения:

    На одну консультацию нельзя создать больше записей, чем предусмотрено ограничением (пример: консультация в 10:00 имеет 2 слота для записи, в 13:30 только 1 слот).

    У одного абитуриента может быть только одна активная запись на консультацию (например, уникальность email).

  3. Функциональные требования:
    Форма записи на консультацию.

     API зарегистрированных абитуриентов на консультации.

Требования к реализации:


Обязательные требования:

  • Laravel: версия 9+ (если есть возможность — 10 или 11).

  • Использовать миграции для создания таблиц.

  • Использовать Eloquent-модели и связи.

  • Поступающие данные должны проходить валидацию.

Формат сдачи:
Ссылка на Git-репозиторий (GitHub / GitLab). В репозитории должен быть файл README.md, где:

  • кратко описано, что реализовано;

  • указано, какую версию PHP/Laravel использовать;

  • описаны шаги запуска проекта (миграции, пример .env, команда php artisan serve);

  • по желанию — примеры запросов.

Критерии оценки:
Мы смотрим не на «идеальность», а на:

  • Структуру кода.

  • Понятные названия классов, методов и переменных.

  • Работу с Laravel: миграции, модели, связи, FormRequest и валидация, пагинация, фильтры.

  • Краткие, но понятные комментарии там, где логика неочевидна.

Это основа. Позже, в неформальной беседе, я выяснил, что ТЗ — это каркас. По этому каркасу нужно идти, но если есть идеи, можно (и даже нужно) добавить что-то своё. Эта оговорка стала зелёным светом для главной моей импровизации. Но обо всём по порядку.

2.2 Проект и как с ним работать

2.2.1 Подготовка к разработке

Требуемый стек.
Требуемый стек.

Если вы захотите запустить этот проект локально (или повторить мой путь), вот краткий гайд по настройке окружения. Отмечу, что у меня изначально не были установлены ни Composer, ни Laravel, поэтому первые шаги были посвящены именно их настройке.

Требуемый стек (версии могут быть выше):

  • PHP: 8.1+.

  • Laravel: 10.x.

  • Composer: 2.x.

  • СУБД: MySQL 8.

  • Node.js: 18+.

Пошаговая настройка (на моём примере, если быть точным, то на Windows 11 с использованием Chocolatey):

  1. Установка Node.js.
    У меня он был установлен, но можно скачать с официального сайта Node.js версии 18 или выше.

  2. Установка менеджера пакетов Chocolatey и PHP и Composer через Chocolatey.
    Тоже всё было (кроме Composer) из-за прошлого проекта, но вот как его скачать: откройте PowerShell от имени администратора и выполните команду для установки Chocolatey:

    Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

    В том же PowerShell выполните:

    choco install php -y
    choco install composer -y

    Проверьте установку:

    php -v
    composer --version

    Если не получится скачать, альтернативно composer можно скачать с сайта.

  3. Создание Laravel-проекта.
    Дальше идёт шаг, который я первый раз делал. Перейдите в нужную директорию и создайте проект с помощью Composer (официальный метод):

    composer create-project laravel/laravel test_task
    cd test_task
  4. Установка и настройка MySQL.
    MySQL уже был установлен, но нашёл такой способ: установите MySQL через Chocolatey (choco install mysql -y) .

  5. Базовый тест.
    Запустите встроенные тесты, чтобы убедиться, что каркас работает:

    php artisan test

    Или запустите сам проект:

    php artisan serve

    После этих шагов вы получите чистую установку Laravel, готовую к разработке. В моём случае именно с этого момента началась настоящая работа над самим сервисом.

2.2.2 Кратко про проект:

Дальше статья пойдёт про устройство проекта: какие архитектурные решения были разработаны и как они работают в общем. Если вам интересны в первую очередь выводы и итоги — смело переходите к финальному разделу статьи.

Итак, всё начинается с данных. Исходя из ТЗ, мы имеем две ключевые сущности:

Консультация (Consultation) — центральный объект системы. Должна хранить:

  • Дату и время проведения.

  • Тип: индивидуальная или групповая.

  • Формат: очно или онлайн.

  • Максимальное количество участников (слотов).

Запись (Registration) — факт регистрации абитуриента. Должна хранить:

  • Связь с конкретной консультацией.

  • Контактные данные абитуриента (имя, email, телефон).

  • Статус записи (активна, отменена).

Вот как выглядит итоговая структура основных каталогов проекта:

├───app
│   ├───Http
│   │   ├───Controllers      # Контроллеры для обработки запросов
│   │   │   ├───Admin        # Логика админ-панели (не по тз)
│   │   │   └───Api          # Контроллеры для REST API
│   │   ├───Middleware       # Промежуточное ПО 
│   │   └───Requests         # Классы FormRequest для валидации
│   ├───Models               # Eloquent-модели (Consultation, Registration)
│   └───Providers            # Сервис-провайдеры
├───database
│   ├───migrations           # Файлы миграций для создания таблиц
│   └───seeders              # Наполнители тестовыми данными
├───resources
│   └───views
│       ├───admin            # Blade-шаблоны админ-раздела
│       ├───consultations    # Шаблоны для просмотра консультаций
│       ├───layouts          # Основные макеты сайта
│       └───registration     # Шаблоны формы записи
├───routes
│   ├───api.php              # Маршруты для REST API
│   ├───web.php              # Основные web-маршруты
│   └───admin.php            # Маршруты админ-панели 
└───tests                    # Тесты (базовый класс)

Здесь появляется моя ключевая импровизация. ТЗ явно описывает только одного пользователя — абитуриента. Но любой жизнеспособный сервис требует администрирования. Так родилась простая, но эффективная двух ролевая модель:

  • Абитуриент: видит только публичную часть, список доступных консультаций и форму записи. От него скрыта вся внутренняя кухня: управление расписанием, список записавшихся.

  • Администратор: видит всё. Может создавать, редактировать и удалять консультации, просматривать все записи. Для него «туман» рассеян.

2.2.3. Система аутентификации: разбираем контроллеры Laravel.

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

Логика приложения

ConfirmPasswordController.php — дополнительная защита важных действий

Этот контроллер отвечает за подтверждение пароля перед выполнением критических операций. Представьте, что вы уже вошли в систему, но хотите изменить настройки безопасности — фреймворк попросит вас повторно ввести пароль для подтверждения личности.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ConfirmsPasswords;

class ConfirmPasswordController extends Controller
{
    /* 
    * Контроллер подтверждения пароля
    */
    
    use ConfirmsPasswords;
    protected $redirectTo = '/home';

    public function __construct()
    {
        $this->middleware('auth');
    }
}

Ключевые моменты:

  • Трейт ConfirmsPasswords содержит всю логику проверки пароля и сброса таймера подтверждения.

  • $redirectTo = '/home' определяет, куда пользователь будет перенаправлен после успешного подтверждения. В моём проекте этот путь был изменён на админ-панель.

  • Middleware auth гарантирует, что доступ к этому функционалу есть только у уже аутентифицированных пользователей.

ForgotPasswordController.php — восстановление доступа

Контроллер для первого шага восстановления пароля — запроса ссылки на сброс. Пользователь вводит email, система проверяет его существование и отправляет письмо со специальным токеном.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;

class ForgotPasswordController extends Controller
{
    use SendsPasswordResetEmails;
}

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

LoginController.php — вход в систему

Сердце системы аутентификации. Этот контроллер обрабатывает как отображение формы входа, так и попытку авторизации.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = '/consultations';
    protected $redirectAfterLogout = '/';

    public function __construct()
    {
        $this->middleware('guest')->except('logout');
        $this->middleware('auth')->only('logout');
    }
}

Интересный нюанс: если присмотреться к трейту, можно обнаружить метод username(), который возвращает 'email'. Это значит, что в системе используется email как уникальный идентификатор вместо логина — стандартная современная практика.

RegisterController.php — регистрация новых пользователей

Самый кастомизированный контроллер в нашем проекте. По умолчанию Laravel запрашивает только имя, email и пароль. Но для сервиса записи на консультации нужна дополнительная информация.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller
{
    use RegistersUsers;

    protected $redirectTo = '/consultations';

    public function __construct()
    {
        $this->middleware('guest');
    }

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
            'phone' => ['required', 'string', 'max:20'],
        ]);
    }

    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'role' => 'student',
            'phone' => $data['phone'],
            'email_verified_at' => now(),
        ]);
    }
}

Ключевые изменения и их обоснование:

  1. Добавлено поле phone в валидатор и метод create(). Телефон необходим для обратной связи с абитуриентом — бизнес-требование, выходящее за рамки стандартной аутентификации.

  2. Автоматическое присвоение роли: 'role' => 'student'. Все, кто регистрируется через публичную форму, по умолчанию становятся студентами. Администраторов создают другие администраторы напрямую через БД.

  3. Мгновенная верификация email: 'email_verified_at' => now(). В учебном проекте я упростил процесс, чтобы не настраивать почту. В реальном проекте здесь должна быть отправка письма с подтверждением, а поле email_verified_at заполняется после перехода по ссылке из письма.

ResetPasswordController.php — завершение сброса пароля

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

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;

class ResetPasswordController extends Controller
{
    use ResetsPasswords;
    protected $redirectTo = '/home';
}

Хотя контроллер выглядит минималистично, трейт ResetsPasswords выполняет важную работу:

  1. Проверяет токен из URL с токеном в таблице password_resets.

  2. Валидирует новый пароль (минимальная длина, подтверждение).

  3. Обновляет хеш пароля в таблице users.

VerificationController.php — подтверждение email

Контроллер для верификации email-адреса, который становится актуальным, когда мы отказываемся от упрощения email_verified_at => now() и реализуем полноценную отправку писем.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;

class VerificationController extends Controller
{
    use VerifiesEmails;

    protected $redirectTo = '/home';

    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('signed')->only('verify');
        $this->middleware('throttle:6,1')->only('verify', 'resend');
    }
}

Интересные детали:

  • Middleware signed используется только для метода verify. Он гарантирует, что ссылка для подтверждения не была подделана.

  • Middleware throttle:6,1 ограничивает количество попыток верификации: не более 6 попыток в минуту. Это защита от перебора токенов.

В моём проекте этот контроллер фактически не используется из-за упрощения с мгновенной верификацией, но в production-версии он стал бы критически важным для обеспечения доверия к системе (представьте запись на важную консультацию с неподтверждённым email).

Вывод по системе аутентификации

Laravel предоставляет готовый, но гибко настраиваемый каркас для безопасности. Эта система создаёт надёжный фундамент, на котором строится вся дальнейшая логика приложения.

2.2.4. Контроллеры бизнес-логики и Middleware: сердце приложения.

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

Подробности

AdminController.php — панель управления консультациями

Этот контроллер — моя главная импровизация, выходящая за рамки ТЗ. Он реализует полноценную CRUD-панель для администратора, превращая сервис из простой формы записи в управляемую систему.

<?php
namespace App\Http\Controllers;
// ... импорты

class AdminController extends Controller
{
    public function index()
    {
        // Простая статистика для дашборда
        $stats = [
            'consultations' => Consultation::count(),
            'registrations' => Registration::count(),
            'users' => User::count(),
            'active_consultations' => Consultation::where('is_active', true)->count(),
        ];
        // ... получение последних записей и консультаций
        return view('admin.simple', compact('stats', 'recentRegistrations', 'consultations'));
    }
    
    // Основной CRUD для консультаций
    public function consultations()
    {
        $consultations = Consultation::withCount('registrations')
            ->orderBy('start_time', 'desc')
            ->paginate(15);
        return view('admin.consultations_list', compact('consultations'));
    }
    
    public function storeConsultation(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'title' => 'required|string|max:255',
            'type' => 'required|in:individual,group',
            'format' => 'required|in:online,offline',
            'start_time' => 'required|date',
            'max_slots' => 'required|integer|min:1',
        ]);
        // ... создание консультации
    }
    
    /**
     * Экспорт записей на консультацию в CSV
     */
    public function exportConsultationRegistrations($id)
    {
        $consultation = Consultation::with('registrations')->findOrFail($id);
        $fileName = 'consultation_' . $consultation->id . '_participants_' . date('Y-m-d') . '.csv';
        // ... формирование CSV с BOM для кодировки
        return new StreamedResponse(function() use ($consultation) {
            // ... запись данных
        }, 200, $headers);
    }
}

Ключевые особенности:

  1. Динамический дашборд с живой статистикой (count()where()) даёт администратору моментальную картину состояния системы.

  2. Пагинация через paginate(15) — обязательное требование ТЗ, реализованное одной строкой. Позволяет работать с большими объёмами данных без перегрузки интерфейса.

  3. Экспорт в CSV — дополнительная фича, показывающая понимание потребностей администратора. Особенно важно:

  4. Безопасное удаление с проверкой registrations()->count() > 0 предотвращает потерю данных пользователей.

Controller.php — базовый класс всех контроллеров

Фундаментальный класс, от которого наследуются все контроллеры приложения (кроме аутентификации).

<?php
namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
    use AuthorizesRequests, ValidatesRequests;
}

Важность минимализма:

  • AuthorizesRequests — предоставляет метод authorize(), интегрирующийся с политиками (Policies) Laravel.

  • ValidatesRequests — даёт методы validate() и validator(), которые используются во всех дочерних контроллерах. Это централизация логики валидации.

  • Наследование от BaseController Laravel обеспечивает интеграцию с ядром фреймворка.

Такой подход соответствует принципу DRY (Don’t Repeat Yourself) 

HomeController.php — редирект после авторизации

Простой, но важный контроллер, который определяет «точку входа» после успешного логина.

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }
    
    public function index()
    {
        return view('home');
    }
}

Конфликт и его решение: В ТЗ не было явного упоминания о домашней странице, но Laravel по умолчанию создаёт маршрут /home. В моём проекте возникла дилемма:

  1. Оставить стандартный /home (как в этом контроллере).

  2. Перенаправить сразу в полезную часть приложения.

Я выбрал второй путь, изменив$redirectTo в LoginController и RegisterController на /consultations. Этот контроллер остался как артефакт, но фактически не используется. В production-версии его стоит удалить или перепрофилировать под персонализированную dashboard-страницу.

RegistrationController.php — ядро выполнения ТЗ.

Самый важный контроллер с точки зрения выполнения технического задания. Он реализует обе основные функции: веб-интерфейс записи и REST API.

<?php
namespace App\Http\Controllers;

use App\Models\Consultation;
use App\Models\Registration;
use Illuminate\Support\Facades\Auth;

class RegistrationController extends Controller
{
    // Публичный список консультаций
    public function index()
    {
        $consultations = Consultation::where('is_active', true)
            ->where('start_time', '>', now())
            ->orderBy('start_time')
            ->get();
        return view('consultations.index', compact('consultations'));
    }
    
    // Запись с автозаполнением для авторизованных
    public function create($id)
    {
        $consultation = Consultation::findOrFail($id);
        
        // Проверка доступности слотов
        if (!$consultation->hasAvailableSlots()) {
            return redirect()->route('consultations.index')
                ->with('error', 'На эту консультацию нет свободных мест');
        }
        
        // Автозаполнение из профиля пользователя
        $userData = [];
        if (Auth::check()) {
            $user = Auth::user();
            $nameParts = explode(' ', $user->name);
            $userData = [
                'first_name' => $nameParts[0] ?? '',
                'last_name' => $nameParts[1] ?? '',
                'email' => $user->email,
                'phone' => $user->phone ?? '',
            ];
        }
        return view('registration.form', compact('consultation', 'userData'));
    }
    
    // API endpoint для внешних сервисов
    public function apiStore(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'consultation_id' => 'required|exists:consultations,id',
            'first_name' => 'required|string|max:100',
            'email' => [
                'required',
                'email',
                function ($attribute, $value, $fail) use ($request) {
                    $exists = Registration::where('email', $value)
                        ->where('consultation_id', $request->consultation_id)
                        ->exists();
                    if ($exists) {
                        $fail('Этот email уже зарегистрирован на данную консультацию');
                    }
                }
            ],
        ]);
        // ... создание записи через API
    }
}

AdminMiddleware.php — защита административной зоны

Кастомный middleware, который ограничивает доступ к админ-панели только пользователям с ролью admin.

<?php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class AdminMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        // Двухуровневая проверка
        if (!Auth::check()) {
            return redirect('/login')->with('error', 'Пожалуйста, войдите в систему.');
        }

        if (Auth::user()->role !== 'admin') {
            return redirect('/consultations')->with('error', 'Доступ запрещен...');
        }

        return $next($request);
    }
}

Middleware подключается в Kernel.php и применяется ко всем админ-маршрутам.

2.2.5. FormRequest, Kernel и модели:

Подробности

StoreRegistrationRequest.php — продвинутая валидация данных

Этот класс демонстрирует мощь Laravel в обработке и валидации входных данных. Вместо простой валидации в контроллере, используется специализированный FormRequest.

<?php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreRegistrationRequest extends FormRequest
{
    public function authorize()
    {
        return true; // Доступ всем, так как запись открыта
    }

    public function rules()
    {
        return [
            'consultation_id' => 'required|exists:consultations,id',
            'first_name' => 'required|string|max:100|regex:/^[a-zA-Zа-яА-ЯёЁ\s\-]+$/u',
            'last_name' => 'required|string|max:100|regex:/^[a-zA-Zа-яА-ЯёЁ\s\-]+$/u',
            'email' => [
                'required',
                'email',
                Rule::unique('registrations', 'email')->where(function ($query) {
                    return $query->where('consultation_id', $this->consultation_id);
                })
            ],
            'phone' => [
                'required',
                'string',
                function ($attribute, $value, $fail) {
                    $cleanPhone = preg_replace('/[^0-9]/', '', $value);
                    
                    if (strlen($cleanPhone) !== 11) {
                        $fail('Номер телефона должен содержать 11 цифр');
                    }
                    
                    if (!in_array($cleanPhone[0], ['7', '8'])) {
                        $fail('Номер телефона должен начинаться с 7 или 8');
                    }
                }
            ]
        ];
    }

    protected function prepareForValidation()
    {
        if ($this->has('phone')) {
            $phone = $this->input('phone');
            $cleanPhone = preg_replace('/[^0-9]/', '', $phone);
            
            // Нормализация: 8 -> 7
            if (strlen($cleanPhone) === 11 && $cleanPhone[0] === '8') {
                $cleanPhone = '7' . substr($cleanPhone, 1);
            }
            
            // Форматирование для отображения
            $formattedPhone = $cleanPhone;
            if (strlen($cleanPhone) === 11) {
                $formattedPhone = '+7(' . substr($cleanPhone, 1, 3) . ')' . 
                                  substr($cleanPhone, 4, 3) . '-' .
                                  substr($cleanPhone, 7, 2) . '-' .
                                  substr($cleanPhone, 9, 2);
            }
            
            $this->merge([
                'phone' => $formattedPhone,
                'phone_raw' => $cleanPhone // сохраняем сырой вариант
            ]);
        }
    }
}

Ключевые моменты реализации:

  1. Составное правило уникальности для email:

    Rule::unique('registrations', 'email')->where(function ($query) {
        return $query->where('consultation_id', $this->consultation_id);
    })

    Это прямое воплощение бизнес-ограничения из ТЗ: «У одного абитуриента может быть только одна активная запись на консультацию». Email должен быть уникален в рамках конкретной консультации.

  2. Кастомная валидация телефона: вместо стандартных правил используется замыкание, которое использует очищение номер от любых нецифровых символов, проверяет длину (ровно 11 цифр для российских номеров) и код страны (7 или 8)

  3. Кастомные сообщения об ошибках: в методе messages() делают интерфейс более дружелюбным.

Использование FormRequest — это лучшая практика Laravel. Она отделяет валидацию от бизнес-логики, делает код контроллеров чище и позволяет повторно использовать правила валидации.

Kernel.php — конвейер обработки запросов

Ядро приложения, управляющее всеми middleware — промежуточными слоями, обрабатывающими HTTP-запросы.

<?php
namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $middleware = [
        \App\Http\Middleware\TrustProxies::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    protected $middlewareAliases = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'admin' => \App\Http\Middleware\AdminMiddleware::class, // Наш кастомный middleware
    ];
}

Models: Consultation.php, Registration.php, User.php.

Consultation.php — модель консультаций с бизнес-логикой:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Consultation extends Model
{
    protected $casts = [
        'start_time' => 'datetime',
        'is_active' => 'boolean',
        'registered_count' => 'integer'
    ];

    public function hasAvailableSlots()
    {
        return $this->registered_count < $this->max_slots;
    }

    public function availableSlots()
    {
        return $this->max_slots - $this->registered_count;
    }
}

Важные аспекты:

  • Кастинг атрибутов через $casts: даты становятся Carbon-объектами, булевы значения — настоящими boolean. Это упрощает работу в шаблонах и логике.

  • Бизнес-методы непосредственно в модели: hasAvailableSlots(), availableSlots(). Это соответствует принципу «Fat Models, Thin Controllers».

  • Связь hasMany с регистрациями — основа для подсчёта записей.

Registration.php — модель записей:

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Registration extends Model
{
    use HasFactory;

    protected $fillable = ['consultation_id', 'first_name', 'last_name', 'email', 'phone'];

    public function consultation()
    {
        return $this->belongsTo(Consultation::class);
    }
}

User.php — расширенная модель пользователя:

<?php
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    protected $fillable = ['name', 'email', 'password', 'role', 'phone'];
    
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed', // Автоматическое хеширование
        ];
    }

    public function isAdmin(): bool
    {
        return $this->role === 'admin';
    }
}

Ключевые улучшения:

  1. Каст password => 'hashed' — нововведение Laravel 9+. Пароль автоматически хэшируется при присвоении: $user->password = 'secret'.

  2. Методы-хелперы isAdmin()isStudent() — делают код контроллеров читаемее: вместо $user->role === 'admin' можно писать $user->isAdmin().

2.2.6. Работа с базой данных: миграции и сидеры.

Если модели — это «лицо» ваших данных в приложении, то миграции — их «скелет», а сидеры — «начальное наполнение». Вместе они образуют мощную систему управления базой данных, которая эволюционирует вместе с приложением.

База данных
База данных

Исторически разработчики хранили SQL-дампы базы данных в отдельных файлах, что приводило к проблемам. Laravel решает эту проблему через миграции — PHP-классы, которые описывают изменения в БД и могут выполняться в любом порядке на любой среде.

Представьте, что вы строите дом. Миграции — это не просто чертёж, а пошаговая инструкция по строительству: «сначала залей фундамент, потом возведи стены, затем установи окна». Если нужно что-то изменить, вы не перерисовываете весь чертёж, а добавляете новую инструкцию: «установи дополнительную дверь».

Подробности

Структура таблиц: от требований ТЗ к SQL-схеме

Давайте посмотрим, как требования ТЗ превращаются в реальные таблицы:

1. Таблица consultations (консультации):

Schema::create('consultations', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('type'); // individual, group
    $table->string('format'); // online, offline
    $table->dateTime('start_time');
    $table->integer('max_slots');
    $table->integer('registered_count')->default(0);
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});

2. Таблица registrations (записи):

Schema::create('registrations', function (Blueprint $table) {
    $table->id();
    $table->foreignId('consultation_id')->constrained()->onDelete('cascade');
    $table->string('first_name');
    $table->string('last_name');
    $table->string('email');
    $table->string('phone');
    $table->timestamps();
    
    // Уникальность email на консультацию
    $table->unique(['email', 'consultation_id']);
});
  • Внешний ключ с cascade — при удалении консультации автоматически удаляются все связанные записи.

  • Составной уникальный индекс ['email', 'consultation_id'] — прямое воплощение требования ТЗ: «У одного абитуриента может быть только одна активная запись на консультацию».

  • Разделение имени на first_name и last_name — лучше, чем одно поле name, упрощает сортировку и поиск.

3. Эволюция таблицы users:
Интересная ситуация возникла с таблицей пользователей. Laravel по умолчанию создаёт миграцию для users, но в ТЗ не было явного требования к авторизации! Тем не менее для админ-панели она понадобилась.

Вместо того чтобы редактировать стандартную миграцию, я создал дополнительную миграцию, которая расширяет существующую таблицу:

// Добавляем колонки role и phone если их нет
Schema::table('users', function (Blueprint $table) {
    if (!Schema::hasColumn('users', 'role')) {
        $table->string('role')->default('student')->after('password');
    }
    if (!Schema::hasColumn('users', 'phone')) {
        $table->string('phone')->nullable()->after('role');
    }
});

Это демонстрирует важный принцип: миграции должны быть идемпотентными (безопасными для повторного запуска). Проверка hasColumn гарантирует, что миграция не сломается, если столбец уже существует.

Сидеры: тестовые данные как документация

Сидеры — это «оживление» пустой схемы базы данных. Они выполняют две ключевые функции:

  1. Тестирование — позволяют проверить работу приложения с реалистичными данными.

  2. Документирование — показывают, какие данные ожидаются в системе.

UserSeeder — создание ролевой модели:

User::create([
    'name' => 'Администратор',
    'email' => 'admin@mirea.ru',
    'password' => Hash::make('1111'), 
    'role' => 'admin',
    'phone' => '+79990001122'
]);

User::create([
    'name' => 'Иван Иванов',
    'email' => 'student1@mirea.ru',
    'password' => Hash::make('student1'), 
    'role' => 'student',
    'phone' => '+79991234567'
]);

Обратите внимание на следующие компоненты:

  • Хеширование паролей через Hash::make() — никогда не храните пароли в открытом виде!

  • Домен @mirea.ru — соответствие контексту ТЗ (РТУ МИРЭА).

  • Простые пароли для тестирования — в production такое недопустимо.

ConsultationSeeder — демонстрация типов консультаций:

Consultation::create([
    'title' => 'Консультация по программированию',
    'type' => 'group',
    'format' => 'online',
    'start_time' => Carbon::now()->addDays(1)->setTime(10, 0, 0),
    'max_slots' => 3,
]);

Сидер создаёт все варианты, предусмотренные ТЗ:

  • Групповые и индивидуальные консультации.

  • Онлайн и офлайн форматы.

  • Разное количество слотов (1, 3, 5).

Рабочий процесс с миграциями в командной строке

Вот как выглядит типичный цикл работы с БД в Laravel:

# Создание новой миграции
php artisan make:migration add_status_to_registrations

# Выполнение всех миграций
php artisan migrate

# Откат последней миграции
php artisan migrate:rollback

# Сброс всей базы (удаление всех таблиц)
php artisan migrate:reset

# Полный перезапуск (сброс + миграции + сидеры)
php artisan migrate:fresh --seed

# Запуск только сидеров
php artisan db:seed

Важный момент: миграции выполняются в порядке, определённом по timestamp в названии файла. Именно поэтому в моём проекте есть миграция 2024_01_15_000001_create_cache_table.php — она должна выполняться первой, так как кэш требуется другим компонентам Laravel.

Вывод: база данных как версионируемый компонент

Система миграций Laravel превращает базу данных из «чёрного ящика» в полноценную часть кодовой базы, которая:

В моём проекте эта система позволила быстро итератировать над структурой данных, не боясь «сломать» базу. Когда пришло время сдавать задание, достаточно было предоставить команду php artisan migrate --seed, чтобы развернуть полную базу с тестовыми данными.

2.2.6. Фронтенд-архитектура: модульные стили и единый дизайн

Когда проект начинался, CSS-стили были разбросаны по Blade-шаблонам — типичная ошибка новичка. По мере роста приложения стало ясно: нужно систематизировать стили, чтобы их можно было поддерживать и масштабировать. Результат — модульная структура, где каждый компонент интерфейса имеет свою зону ответственности.

Дерево стилей: от хаоса к системе

public/css/
├── style.css           # Базовые стили и утилиты (фундамент)
├── admin.css          # Стили админ-панели
├── auth.css           # Стили аутентификации
├── consultations.css  # Страницы консультаций
└── registration.css   # Формы записи
Подробности

Философия разделения:

  • style.css — общий фундамент: сброс стилей, базовые компоненты (кнопки, формы, карточки), утилитарные классы.

  • Модульные CSS-файлы — стили для конкретных страниц или функциональных блоков.

Ключевые дизайн-решения

1. Единая цветовая палитра (взята из style.css):

--primary: #3498db;      /* Основной синий */
--success: #27ae60;      /* Зелёный для успеха */
--danger: #e74c3c;       /* Красный для ошибок */
--dark: #2c3e50;         /* Тёмный для текста */
--light: #f5f5f5;        /* Светлый фон */

Все цвета вынесены в переменные (на практике — повторяются в каждом файле, можно было использовать CSS-переменные).

2. Последовательная иерархия заголовков:

/* Заголовки везде выглядят единообразно */
.consultations-header, .registration-header, .admin-header {
    background: linear-gradient(135deg, #0c2461, #1e3799);
    padding: 20px 30px;
    border-radius: 12px;
    margin-bottom: 25px;
}

Градиентный фон, скруглённые углы, отступы — создают фирменный стиль РТУ МИРЭА.

3. Адаптивность как must-have:

@media (max-width: 768px) {
    .consultations-header {
        flex-direction: column;
        text-align: center;
    }
    .btn {
        width: 100%;
        margin-bottom: 10px;
    }
}

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

Интересные находки в коде

Умные карточки для тестовых аккаунтов (auth.css):

.account-card:nth-child(1)::before { background: #e74c3c; } /* Админ */
.account-card:nth-child(2)::before { background: #27ae60; } /* Студент */

Цветная полоска сверху сразу показывает роль пользователя — маленькая, но полезная деталь UX.

Единая система уведомлений:

/* Уведомления используют border-left для цветового кодирования */
.notification-success { border-left-color: #27ae60; }
.notification-error { border-left-color: #e74c3c; }
.notification-info { border-left-color: #3498db; }

Визуальная согласованность: пользователь учится «читать» статус по цвету.

Гибкая сетка для деталей:

.consultation-details {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 12px;
}

Дизайн сознательно сделан консервативным и функциональным — это не модный лендинг, а инструмент для работы. Синий градиент — отсылка к корпоративному стилю РТУ МИРЭА, понятная навигация, чёткие кнопки действий.

Фронтенд в этом проекте решает конкретные задачи: направляет пользователя, предоставляет информацию, предотвращает ошибки через визуальные подсказки. И делает это единообразно на всех страницах.

2.2.7. Blade-шаблоны: от витрины до панели управления

Blade — шаблонизатор Laravel, который превращает PHP-код в динамический HTML. В проекте реализована модульная структура шаблонов, где каждая страница решает конкретную задачу.

Подробности

Архитектура шаблонов: логическое дерево

views/
├── welcome.blade.php      # Лендинг → точка входа для гостей
├── home.blade.php         # Личный кабинет → после авторизации
├── consultations/         # Публичная часть ТЗ
│   └── index.blade.php    # Витрина консультаций → ядро ТЗ
├── registration/          # Процесс записи (ТЗ п.3.1)
│   ├── form.blade.php     # Форма записи → сбор данных
│   ├── my.blade.php       # Мои записи → личный журнал
│   └── success.blade.php  # Подтверждение → фиксация успеха
├── admin/                 # Админка (импровизация)
│   ├── index.blade.php    # Дашборд → общая статистика
│   ├── consultations_list.blade.php # Управление → консультаций
│   └── registrations_list.blade.php # Просмотр записей → мониторинг
└── auth/                  # Система входа (база Laravel)
    ├── login.blade.php    # Авторизация → доступ к системе
    └── register.blade.php # Регистрация → создание аккаунта

Анализ ключевых шаблонов

1. welcome.blade.php — визитная карточка системы

<!-- Пример из кода: -->
<div class="test-accounts">
    <h3> Тестовые аккаунты</h3>
    <div class="account-card">
        <h4>Администратор</h4>
        <p><strong>Логин:</strong> admin@mirea.ru</p>
        <p><strong>Пароль:</strong> 1111</p>
    </div>
</div>

Ключевые особенности:

  • Приветственный экран с логотипом РТУ МИРЭА.

  • Тестовые аккаунты — удобство для проверяющих (явно не требовалось в ТЗ, но без такой возможности сложно проверить то, что нужно будет удалить при улучшении или подключении системы).

2. consultations/index.blade.php — выполнение пункта 3.1 ТЗ

Цель: «Окно регистрации для абитуриента, где отображаются услуги и доступные слоты».

<!-- Блок соответствия ТЗ: -->
<div class="consultation-card">
    <h3>{{ $consultation->title }}</h3>
    <div class="consultation-details">
        <div class="detail-item">
            <strong>Свободных мест:</strong> 
            <span class="consultation-badge {{ $consultation->hasAvailableSlots() ? 'badge-success' : 'badge-danger' }}">
                {{ $consultation->availableSlots() }} / {{ $consultation->max_slots }}
            </span>
        </div>
    </div>
    @if($consultation->hasAvailableSlots())
        <a href="/registration/{{ $consultation->id }}" class="btn-consultations btn-primary">Записаться</a>
    @else
        <button class="btn-consultations btn-disabled" disabled>Нет свободных мест</button>
    @endif
</div>

Ключевые особенности:

  • Визуализация ограничения слотов — цветные бейджи (зелёный/красный).

  • Защита от гостей — неавторизованных перенаправляет на логин.

  • Единый заголовок с гербом вуза — поддержание корпоративного стиля.

3. registration/my.blade.php — личный кабинет студента

Цель: Дать пользователю возможность просмотреть свои записи (логичное расширение ТЗ).

<!-- Персонализация и данные: -->
<div class="registration-message message-info">
    <p><strong>Добро пожаловать, {{ $user->name }}!</strong> Ваш email: {{ $user->email }}</p>
</div>

@foreach($registrations as $registration)
    <div class="registration-item">
        <h3>{{ $registration->consultation->title }}</h3>
        <div class="registration-item-details">
            <div class="detail-item-info">
                <strong>Дата и время:</strong> 
                {{ $registration->consultation->start_time->format('d.m.Y H:i') }}
            </div>
        </div>
    </div>
@endforeach

Ключевые особенности:

  • Персонализированное приветствие — использует данные из Auth::user().

  • Детализация записей — показывает всё — от названия консультации до контактов.

  • Навигация в контексте — кнопки ведут к другим разделам системы

4. admin/index.blade.php — дашборд администратора

Цель: «Панель управления консультациями» — моя основная импровизация сверх ТЗ.

<!-- Статистика и визуализация: -->
<div class="admin-stats">
    <div class="stat-card">
        <div class="stat-number">{{ $totalConsultations }}</div>
        <div class="stat-label">Всего консультаций</div>
    </div>
</div>

<div class="chart-container">
    @foreach($consultations as $consultation)
        @php $percent = min(100, round(($consultation->registrations_count / $consultation->max_slots) * 100)) @endphp
        <div class="chart-bar">
            <div class="chart-fill" style="width: {{ $percent }}%"></div>
            <div class="chart-percent">{{ $consultation->registrations_count }}/{{ $consultation->max_slots }}</div>
        </div>
    @endforeach
</div>

Ключевые особенности:

  • Дашборд с KPI — 4 ключевых метрики системы.

  • Последние активности — таблицы со свежими данными.

  • Полная навигация — меню ко всем админразделам.

Итог: каждый шаблон решает конкретную задачу, выдержан в общем стиле (CSS) и следует правилам Laravel. На выходе превращая формально-сухое ТЗ в понятный интерфейс.

2.2.8. Маршрутизация: карта приложения и архитектурные решения

Файл web.php — это сердце маршрутизации. В этом файле прописано, где он находится, кто имеет право туда заходить и что происходит, когда пользователь делает запрос. По сути, это карта, по которой движется весь трафик в системе.

Подробности

Структура маршрутов: три уровня доступа

Schema::create('consultations', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('type'); 
    $table->string('format');
    $table->dateTime('start_time');
    $table->integer('max_slots');
    $table->integer('registered_count')->default(0);
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});

Ключевые архитектурные решения

1. Выполнение требований ТЗ по API (пункт 3.2).

// API публичное - по заданию должно быть доступно внешним сервисам
Route::get('/api/consultations', [RegistrationController::class, 'apiIndex']);
Route::post('/api/register', [RegistrationController::class, 'apiStore']);

2. Двойная защита админки: middleware + ручная проверка.

Route::middleware(['auth'])->group(function () {
    Route::prefix('admin')->group(function () {
        Route::get('/', function () {
            if (!Auth::check() || Auth::user()->role !== 'admin') {
                return redirect('/consultations')->with('error', 'Доступ запрещен...');
            }
            // ... логика дашборда
        })->name('admin.index');
    });
});

Почему так, а не через middleware admin? В начале разработки я создал кастомный AdminMiddleware, но затем решил сделать проще для наглядности. В реальном проекте лучше использовать middleware.

3. RESTful-архитектура для админ-панели.

// CRUD для консультаций (импровизация сверх ТЗ)
Route::get('/admin/consultations', ...)->name('admin.consultations');         
Route::get('/admin/consultations/create', ...)->name('admin.consultations.create'); 
Route::post('/admin/consultations', ...)->name('admin.consultations.store');  
Route::get('/admin/consultation/edit/{id}', ...)->name('admin.consultation.edit'); 
Route::post('/admin/consultation/update/{id}', ...)->name('admin.consultation.update'); 
Route::delete('/admin/consultation/delete/{id}', ...)->name('admin.consultation.delete'); 

4. Умные редиректы для UX.

// Перенаправление /home на главную
Route::get('/home', function () {
    return redirect('/consultations');
})->name('home');

Laravel по умолчанию использует /home. Я перенаправил его на /consultations, чтобы не создавать новую страницу.

API-маршруты: точное выполнение ТЗ

Требование ТЗ: «предоставляет API внешним сервисам (JSON-формат ответа)».

// Получить список консультаций
GET /api/consultations → возвращает JSON

// Записаться на консультацию  
POST /api/register → принимает JSON, возвращает JSON

Вывод: Маршрутизация от карты к целостной системе.

В итоге именно эта структура смогла легко обеспечить понятную маршрутизацию: куда ведёт каждая ссылка, что показывать абитуриенту, что — администратору. По сути, она выступила той самой «паутинкой», которая связал все компоненты так, как было написано в ТЗ и дальше больше.


4. Итоги: от ТЗ до работающего сервиса

4.1 Что получилось в результате

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

Дашборд с ключевыми метриками системы.
Дашборд с ключевыми метриками системы.
Публичная витрина для абитуриентов.
Публичная витрина для абитуриентов.
Личный кабинет.
Личный кабинет.
Интерфейс для подробной статистики для администрации.
Интерфейс для подробной статистики для администрации.
 Форма для администратора: создание консультации.
 Форма для администратора: создание консультации.

Урок №1: Практика важнее перфекционизма.
Когда время ограничено, лучше сделать работающий прототип с понятным кодом, чем идеальную систему, которую не успеешь закончить. Мой код далёк от идеала, зато результат работает и решает все задачи ТЗ.

Урок №2: Продуктовое мышление.
Тестовое задание — это не только про код. Нужно думать о том:

  • Как пользователь будет работать с системой?

  • Что нужно администратору для управления?

Урок №3: Что лучше делать всё вовремя, а не откладывать на потом.

  • Можно было сделать лучше, по дизайну и логике.

  • Некоторые вещи приходилось просто вставлять и разбираться походу.

4.3 Что можно было сделать лучше:

Вот что-то похожее на такой календарь
Вот что-то похожее на такой календарь

Я бы хотел внедрить два ключевых момента, о которых тогда не думал:

1. Систему ролей на базе spatie/laravel-permission для более простого управления разрешениями пользователей. 2. Интерактивный календарь для упрощения системы, т. к. сейчас это рассчитано на небольшое количество консультаций.

4.4 Как этот проект помог мне расти как разработчику

Технические навыки и Soft skills:

  • Глубже изучил Laravel и миграции.

  • Освоил работу с Blade-шаблонами.

  • Научился принимать решения о trade-offs (что важно сейчас или можно отложить).

4.5 Заключение

Этот проект стал для меня практическим мостом между теорией и реальной разработкой. Я не просто изучил Laravel по документации, а создал работающий проект для записи на консультации в РТУ МИРЭА, который полностью выполняет техническое задание и готов к доработке для масштабирования в полноценную систему.

Готовый код проекта доступен на GitHub. Если вы начинающий разработчик, надеюсь, мой опыт вдохновит вас браться за сложные задачи. Если опытный — буду благодарен за конструктивную критику, мысли и советы, или просто рассказы о своём опыте, т. к. всегда интересно послушать чужие истории, которые тебе, возможно, помогут в будущем.

P.S.

Финальная мысль: самый сложный проект — это первый. В нём главное — преодолеть барьер «я не знаю, с чего начать». Как только это получается, открывается новый уровень возможностей. Правда, грустный парадокс в том, что после стольких усилий (проектов, практик и собеседований) часто не получаешь обратной связи. Из-за этого можно думать, что всё сделал хорошо, а на деле столкнуться с полным разочарованием.

Вопрос к вам (читателям): Интересно, на что бы вы сделали акцент в первую очередь, оценивая такой проект? И чтобы вы как разработчик добавили с самого начала?

Если кому-то интересно про собеседования, то у нас в компании это не экзамен, а скорее разговор. Главный критерий — ответственность и умение укладываться в сроки. Всё остальное — это нюансы, которые приходят либо с обучением внутри команды, либо с опытом.

Поэтому вопросы обычно сводятся к человеческому: что тебе интересно делать, чего ты ждёшь от работы, какими проектами уже занимался. По сути, это ваша живая версия резюме, где можно рассказать историю из своего опыта, а не просто вспоминать — пересказывать когда-то заученные темы с курсов или из университета.

© 2026 ООО «МТ ФИНАНС»

Комментарии (4)


  1. RblBA
    13.01.2026 18:27

    Успехов автору в его начинаниях и в карьерном росте.

    Не по теме

    Знаю его лично в Химках видал, чем-то странным торговал.


    1. Laborant_Code Автор
      13.01.2026 18:27

      Спасибо за добрые слова!

      Не по теме

      P.S. Насчёт Химок — видимо, у меня есть двойник. Если встретите его снова, передайте, чтобы деревянных котиков не трогал.


  1. Kahelman
    13.01.2026 18:27

    И что это было? Реклама php и lavarel.

    1. Хотелось бы структуру БД которую в итоге миграциями накрутили.

    2. Сколько все это места занимает? Оно понятно что тенраьайты не. Считаем, но чтобы было интререснее надо было поставить ограничение : не более х мбт на диске на все.


    1. Laborant_Code Автор
      13.01.2026 18:27

      Спасибо за отзыв! Это не реклама php и lavarel, а разбор реального тестового задания в компанию, куда я устраивался — пишу первую статью такого формата. Структуру БД и подсчёт места обязательно учту как пожелание на будущее, отличная идея.