Эта статья написана для новичков в программировании, которые только начинают разбираться в архитектуре кода и паттернах проектирования. Если вы уже опытный разработчик (senior+), то, скорее всего, эти мысли покажутся вам очевидными — смело пропускайте или читайте для ностальгии по своим первым архитектурным ошибкам)

Но если вы только учитесь и слышали, что MVC — это «стандарт», который «всегда нужно использовать», то вам может быть полезно узнать, почему это не так и какие подводные камни скрывает этот паттерн. Поехали! ?


Почему MVC все еще живет в 2025 году?

Университеты и курсы продолжают учить MVC как «лучшую практику», штампуя новые поколения разработчиков, которые не знают о существовании шаблонов проектирования, DRY, KISS, SOLID принципов, Clean Architecture, CQRS, DDD или Event‑Driven подходов. Это порочный круг: плохо обученные разработчики создают плохие проекты на MVC. Но MVC кажется простым только на первый взгляд. Джуниоры радостно пихают всю логику в контроллеры, создают «божественные» модели на 2000 строк и думают, что делают все правильно. К тому времени, когда приходит понимание проблем, проект уже превратился в неподдерживаемое легаси.

Несмотря на очевидные недостатки и появление гораздо более эффективных архитектурных решений, MVC продолжает отравлять кодовые базы многих проектов. Причины этого печального явления кроются не в достоинствах паттерна, а в уровне команды которая его потдерживает. Тысячи проектов, построенных на MVC, превратились в монстров, которых страшно трогать. Каждое изменение в одном контроллере может сломать половину системы из-за тесной связанности. Компании предпочитают медленно умирать, поддерживая этот технический долг, вместо того чтобы принять болезненное, но правильное решение о рефакторинге.

На таких проэктах часто можно услышать - "Работает - лучше не трогать".

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

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


Основные проблемы MVC

MVC (Model–View–Controller) – это архитектурный шаблон, предложенный Тригве Ренскаугом в конце 1970., который разделяет код на три части: модель (данные и бизнес-логика), представление (UI) и контроллер (обработчик запросов). Благодаря простоте и наглядности MVC стал де-факто стандартом в веб-разработке: он упрощает понимание приложения и разносит ответственность за разные виды работы. Однако его простота обманчива: при росте проекта даже такой шаблон создаёт «технический долг».

В небольших приложениях MVC отлично работает, но по мере роста системы его слои начинают смешиваться. По мере того как система начинает расти, обрастать новой функциональностью, сложной бизнес-логикой и многочисленными интеграциями, некогда четкие границы MVC начинают размываться. То, что казалось идеальным разделением, постепенно превращается в "толстые" контроллеры, набитые бизнес-логикой, или "жирные" модели, содержащие одновременно правила домена и детали персистентности. Слои начинают смешиваться, превращая элегантную структуру в запутанный клубок зависимостей.

Именно в этот момент мы сталкиваемся с фундаментальной проблемой: MVC, изначально разработанный для относительно простых интерактивных пользовательских интерфейсов, оказывается недостаточно выразительным и устойчивым к изменениям в условиях высоконагруженных, предметно-ориентированных корпоративных систем. Поддержание чистоты слоев становится героической, но часто обреченной на провал борьбой, приводящей к снижению поддерживаемости, усложнению тестирования и замедлению дальнейшего развития.

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

❌ Производительность как жертва архитектуры

MVC-системы работают медленнее по своей природе. Контроллеры тянут за собой огромные зависимости, модели выполняют десятки ненужных запросов к базе данных, а плотная связанность не позволяет эффективно кешировать компоненты. Каждый HTTP-запрос инициализирует половину системы, даже если нужно вернуть простой JSON с одним полем.

❌ Масштабирование - болезненная проблема

Когда проект растет, MVC превращается в кошмар. Нельзя масштабировать отдельные компоненты - приходится дублировать всю систему целиком. Горизонтальное масштабирование требует синхронизации сессий, общих файловых систем и других костылей. Микросервисная архитектура становится невозможной, потому что все завязано друг на друга.

❌ Баги как неизбежность

Попробуйте написать unit-тест для контроллера, который обращается к трем моделям, отправляет email, логирует в файл и редиректит пользователя. Это невозможно без поднятия всей системы. Интеграционные тесты требуют полной базы данных, web-сервера и всех зависимостей. Результат - проекты с покрытием тестами 5-10%, где каждый релиз это русская рулетка.

Без нормального тестирования баги становятся постоянными спутниками MVC-проектов. Изменение одной модели ломает контроллер в другой части системы. Рефакторинг превращается в игру "сапер" - никогда не знаешь, что взорвется. Команды тратят 60-70% времени на исправление багов вместо разработки новых функций.

❌ "Fat Controllers" (Толстые контроллеры)

Это, пожалуй, одна из самых распространенных "болезней" MVC в больших проектах. Изначально контроллеры должны были быть лишь посредниками между моделью и представлением, обрабатывая ввод пользователя и координируя действия. Однако на практике, под давлением быстрых итераций и отсутствия более подходящего места, бизнес-логика начинает мигрировать прямо в методы контроллеров.

Почему это плохо?

  • Нарушение принципа единой ответственности (Single Responsibility Principle - SRP). Контроллер начинает отвечать не только за обработку HTTP-запроса и координацию, но и за бизнес-логику, валидацию, взаимодействие с базой данных и другие аспекты. Это делает его перегруженным и не соответствующим SRP.

  • Сложность тестирования. Толстые" контроллеры трудно тестировать в изоляции. Cлишком много зависимостей. Это делает тесты хрупкими и медленными.

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

  • Высокая связанность (High Coupling). "Толстый" контроллер обычно имеет множество зависимостей от других компонентов (моделей, сервисов, репозиториев, вспомогательных классов), что делает его сильно связанным. Изменение одной зависимости может повлечь за собой изменения в контроллере.

  • Трудности с масштабированием и развитием.

    • Внесение изменений в "толстый" контроллер становится рискованным, так как велик шанс сломать существующую функциональность из-за скрытых зависимостей и переплетения логики.

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

  • Нарушение чистоты архитектурных слоев. Если контроллер содержит бизнес-логику или напрямую обращается к деталям инфраструктуры (например, к SQL-запросам), он нарушает чистоту слоев, размывая границы между Presentation, Application, Domain и Infrastructure.

❌ Отсутствие слоя бизнес-логики

Классический MVC не предлагает явного и самодостаточного слоя для сложной, чистой бизнес-логики. Модель в MVC часто понимается как ORM-объект или ActiveRecord, который одновременно отвечает за данные и за некоторую часть бизнес-правил, тесно переплетая их с деталями персистентности (работой с базой данных). Контроллеры же, как упомянуто выше, становятся свалкой для всего остального. Это приводит к тому, что важнейшие бизнес-правила приложения оказываются "размазанными" по различным компонентам – частично в моделях, частично в контроллерах, а иногда даже прямо в представлениях.

Почему это плохо?

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

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

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

  • Снижение переиспользуемости. Логика, привязанная к конкретным фреймворкам, протоколам (HTTP) или деталям персистентности (SQL-запросы в ORM-моделях), не может быть легко переиспользована.

  • Усложнение масштабирования и поддержки. По мере роста проекта становится все труднее поддерживать его, вносить новые функции и исправлять ошибки, когда нет централизованного и хорошо структурированного места для всей бизнес-логики. Архитектура без доменного слоя склонна к превращению в "большой комок грязи" (Big Ball of Mud).

❌ Проблемы с переиспользованием

Когда бизнес-логика тесно связана с контроллерами или моделями, привязанными к конкретной технологии персистентности, её переиспользование становится серьезной проблемой.

Если эта логика находится в HTTP-контроллере или привязана к специфике веб-запроса, вы столкнетесь с серьезными препятствиями при попытке использовать её для

  • Консольных команд (CLI Commands)

  • Консюмеров очереди сообщений (Message Queue Consumers)

  • API-вызовов (API Calls)

  • Фоновых процессов/Scheduled Jobs

  • Мобильных/Десктопных клиентов (через GraphQL или RPC)

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


Препятствие для применения паттернов проектирования

Одной из самых коварных проблем MVC является то, как он усложняет и даже делает невозможным правильное применение классических паттернов проектирования. Трехслойная структура Model-View-Controller создает архитектурную путаницу, в которой разработчики теряются при попытке разместить паттерны в нужном месте.

Проблема размещения паттернов. Где в MVC должен располагаться Strategy pattern? В модели? Но модель должна работать с данными. В контроллере? Но контроллер должен быть тонким. Создать отдельную папку? Но это нарушает "чистоту" MVC. Результат - паттерны либо не используются вообще, либо впихиваются в неподходящие места, теряя свою эффективность.

Деградация паттернов до антипаттернов. Observer pattern в MVC часто превращается в God Object - модель, которая уведомляет половину системы о своих изменениях. Decorator pattern становится невозможным из-за тесной связи моделей с ActiveRecord. Factory pattern вырождается в статические методы внутри моделей, нарушая принципы ООП.

Невозможность применения Enterprise паттернов. Repository pattern в MVC обычно реализуется как тонкая обертка над ORM, теряя свой смысл. Service Layer сливается с контроллерами или моделями. Unit of Work становится невозможным из-за автоматических коммитов ActiveRecord. Domain Model превращается в Anemic Domain Model - модели без логики, только с данными.

Проблема тестирования паттернов. Даже если паттерн как-то удается разместить в MVC, его становится невозможно протестировать изолированно. Strategy pattern, смешанный с контроллером, требует поднятия всего web-стека для тестирования. Command pattern в модели тянет за собой всю ORM.

Потеря гибкости и переиспользования. Паттерны создаются для повышения гибкости и переиспользования кода. Но в MVC они теряют эти качества, становясь частью монолитной структуры. Нельзя вынести Strategy в отдельный модуль или переиспользовать Command в другом контексте.

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


Нарушения принципов SOLID

Принципы SOLID являются краеугольным камнем для создания гибкого, расширяемого и поддерживаемого объектно-ориентированного кода. Проблемы MVC часто приводят к их нарушению

S - Single Responsibility Principle

  • Принцип: Класс должен иметь только одну причину для изменения.

  • Нарушение принципа: Контроллер имеет 6 разных причин для изменения.

  • Преимущества соблюдения:

    • Легче понять назначение каждого класса

    • Изменения затрагивают только один аспект системы

    • Проще писать unit-тесты для конкретной функциональности

    • Меньше конфликтов при работе в команде

    // ❌ Нарушение в MVC
    class UserController {
        public function register(Request $request) {
            // 1. Валидация HTTP-запроса
            if (empty($request->email) || !filter_var($request->email, FILTER_VALIDATE_EMAIL)) {
                return response()->json(['error' => 'Invalid email'], 400);
            }
            
            // 2. Бизнес-логика
            if (User::where('email', $request->email)->exists()) {
                return response()->json(['error' => 'User exists'], 409);
            }
            
            // 3. Работа с базой данных
            $user = User::create([
                'email' => $request->email,
                'password' => bcrypt($request->password)
            ]);
            
            // 4. Отправка email
            Mail::send('welcome', ['user' => $user], function($m) use ($user) {
                $m->to($user->email)->subject('Welcome!');
            });
            
            // 5. Логирование
            Log::info("User registered: {$user->email}");
            
            // 6. Форматирование ответа
            return response()->json(['user' => $user->toArray()], 201);
        }
    }
    
    // ✅ Правильное решение
    class UserController {
        public function __construct(private UserService $userService) {}
        
        public function register(RegisterRequest $request) {
            $dto = RegisterDTO::fromRequest($request);
            
            $user = $this->userService->register($dto);
          
            return UserResource::make($user);
        }
    }
    
    class UserService {
        public function register(RegisterDTO $dto): User {
            // Только бизнес-логика
        }
    }
    

O - Open/Closed Principle

  • Принцип: Классы должны быть открыты для расширения, но закрыты для модификации.

  • Нарушение принципа: Каждый новый способ оплаты требует изменения контроллера. Для добавления новой функциональности приходится модифицировать существующие контроллеры.

  • Преимущества соблюдения:

    • Можно добавлять новую функциональность без изменения существующего кода

    • Снижается риск внесения багов в работающий код

    • Система становится более гибкой и расширяемой

    • Легче поддерживать обратную совместимость

    // ❌ Нарушение в MVC
    class PaymentController {
        public function process(Request $request) {
            $paymentType = $request->payment_type;
            
            if ($paymentType === 'credit_card') {
                $gateway = new CreditCardGateway();
                $result = $gateway->charge($request->amount, $request->card_data);
            } elseif ($paymentType === 'paypal') {
                $gateway = new PayPalGateway();
                $result = $gateway->pay($request->amount, $request->paypal_token);
            } elseif ($paymentType === 'bitcoin') {
                $gateway = new BitcoinGateway();
                $result = $gateway->transfer($request->amount, $request->wallet);
            }
            // Для добавления нового типа платежа нужно МОДИФИЦИРОВАТЬ контроллер
            
            return response()->json($result);
        }
    }
    
    // ✅ Правильное решение
    interface PaymentGatewayInterface {
        public function process(PaymentData $data): PaymentResult;
    }
    
    class PaymentController {
        public function __construct(
            private PaymentGatewayFactory $factory
        ) {}
        
        public function process(Request $request) {
          
          $gateway = $this->factory->create($request->payment_type);
          
          $data = PaymentData::fromRequest($request);
        
          return $gateway->process($data); // Новые типы добавляются БЕЗ изменения контроллера
        }
    }

L - Liskov Substitution

  • Принцип: Подклассы контроллеров меняют поведение базового класса

  • Нарушение принципа: Нарушают контракт родительского класса

  • Преимущества соблюдения:

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

    • Можно безопасно заменять объекты их наследниками

    • Код становится более предсказуемым

    • Упрощается тестирование через моки и стабы

    // ❌ Нарушение в MVC
    class BaseController {
        public function authenticate(Request $request): User {
            $token = $request->header('Authorization');
            return User::where('api_token', $token)->firstOrFail();
        }
    }
    
    class AdminController extends BaseController {
        public function authenticate(Request $request): User {
            // Нарушение LSP: изменяет поведение родительского метода
            $user = parent::authenticate($request);
            
            if (!$user->isAdmin()) {
                throw new UnauthorizedException('Admin access required');
            }
            
            return $user; // Может вернуть исключение вместо User
        }
    }
    
    class GuestController extends BaseController {
        public function authenticate(Request $request): User {
            // Нарушение LSP: полностью меняет логику
            return new GuestUser(); // Возвращает другой тип объекта
        }
    }
    
    // ✅ Правильное решение
    interface AuthenticatorInterface {
        public function authenticate(Request $request): AuthResult;
    }
    
    class TokenAuthenticator implements AuthenticatorInterface {
        public function authenticate(Request $request): AuthResult {
            // Всегда возвращает AuthResult
        }
    }
    
    class AdminAuthenticator implements AuthenticatorInterface {
        public function authenticate(Request $request): AuthResult {
            // Тоже всегда возвращает AuthResult, но с проверкой админа
        }
    }

I - Interface Segregation Principle

  • Принцип: Клиенты не должны зависеть от интерфейсов, которые они не используют.

  • Нарушение принципа: Контроллер вынужден зависеть от методов, которые не использует.

  • Преимущества соблюдения:

    • Классы не зависят от методов, которые не используют

    • Интерфейсы становятся более понятными и целенаправленными

    • Легче изменять части системы независимо

    • Снижается связанность между компонентами

    // ❌ Нарушение в MVC
    class UserController {
        public function __construct(private UserModel $userModel) {}
        
        public function show($id) {
            // Используем только метод find(), но зависим от ВСЕХ методов модели
            return $this->userModel->find($id);
        }
    }
    
    class UserModel {
        public function find($id) { /* ... */ }
        public function create($data) { /* ... */ }
        public function update($id, $data) { /* ... */ }
        public function delete($id) { /* ... */ }
        public function sendEmail($id) { /* ... */ }
        public function uploadAvatar($id, $file) { /* ... */ }
        public function generateReport($id) { /* ... */ }
        public function syncWithCRM($id) { /* ... */ }
        public function calculateStatistics($id) { /* ... */ }
        // Контроллер зависит от ВСЕХ этих методов, используя только один!
    }
    
    // ✅ Правильное решения
    interface UserFinderInterface {
        public function find(int $id): ?User;
    }
    
    interface UserCreatorInterface {
        public function create(array $data): User;
    }
    
    interface UserEmailerInterface {
        public function sendEmail(User $user, string $template): void;
    }
    
    class UserShowController {
        public function __construct(
            private UserFinderInterface $userFinder // Зависит только от нужного интерфейса
        ) {}
        
        public function show($id) {
            return $this->userFinder->find($id);
        }
    }

D - Dependency Inversion Principle

  • Принцип: Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

  • Нарушение прицыпа: Контроллеры напрямую зависят от конкретных реализаций, а не от абстракций. Нельзя поменять БД с MySQL на PostgreSQL без изменения контроллера. Нельзя поменять SMTP на другой email-сервис. Нельзя поменять один апи гетевей на другой.

  • Преимущества соблюдения:

    • Легко заменять реализации (например, БД или внешние API)

    • Проще писать unit-тесты с моками

    • Система становится более модульной

    • Снижается зависимость от конкретных технологий

    // Нарушение в MVC
    class OrderController {
        public function create(Request $request) {
            // Прямая зависимость от конкретных классов (низкий уровень)
            $database = new MySQLDatabase(); // Жестко привязано к MySQL
            $emailService = new SMTPEmailService(); // Жестко привязано к SMTP
            $paymentGateway = new StripeGateway(); // Жестко привязано к Stripe
            $logger = new FileLogger('/var/log/orders.log'); // Жестко привязано к файлу
            
            // Высокоуровневая логика зависит от низкоуровневых деталей
            $order = $database->insert('orders', $request->all());
            $paymentGateway->charge($order['total'], $request->card_data);
            $emailService->send($order['email'], 'Order confirmation');
            $logger->log("Order created: {$order['id']}");
            
            return response()->json($order);
        }
    }
    
    // ✅ Правильное решение
    interface OrderRepositoryInterface {
        public function create(array $data): Order;
    }
    
    interface EmailServiceInterface {
        public function send(string $to, string $template, array $data): void;
    }
    
    interface PaymentGatewayInterface {
        public function charge(float $amount, array $cardData): PaymentResult;
    }
    
    class OrderController {
        public function __construct(
            private OrderRepositoryInterface $orderRepository,
            private EmailServiceInterface $emailService,
            private PaymentGatewayInterface $paymentGateway
        ) {}
        
        public function create(Request $request) {
            // Высокоуровневая логика зависит от абстракций
            $order = $this->orderRepository->create($request->all());
            $this->paymentGateway->charge($order->total, $request->card_data);
            $this->emailService->send($order->email, 'order_confirmation', ['order' => $order]);
            
            return response()->json($order);
        }
    }

Нарушения DRY (Don't Repeat Yourself)

Проблемы:

  • Дублирование валидационной логики в разных контроллерах

  • Повторение логики форматирования данных

  • Копирование бизнес-правил

Решения:

  • Изменения в валидации делаются в одном месте

  • Код легче тестировать

  • Меньше ошибок при модификации

  • Лучшая читаемость и поддержка

Преимущества соблюдения:

  • Единая точка изменений - правки делаются в одном месте и автоматически применяются везде

  • Снижение количества багов - нет риска забыть обновить дублированный код

  • Ускорение разработки - не нужно копировать и адаптировать похожий код

  • Упрощение тестирования - тестируется одна реализация вместо множества копий

  • Лучшая читаемость - код становится более лаконичным и понятным

  • Легче рефакторинг - изменение логики требует правок только в одном месте

  • Соответствие принципу истины - каждое знание имеет единственное, недвусмысленное представление

  • Экономия времени на поддержку - меньше кода для анализа и отладки

  • Снижение технического долга - нет накопления дублированного кода

  • Улучшение консистентности - везде используется одинаковая логика без расхождений

    // ❌ ПЛОХО: Нарушение принципа DRY
    class UserController {
        public function create() {
            // Дублирование валидации
            if (empty($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
                return ['error' => 'Некорректный email'];
            }
            if (empty($_POST['name']) || strlen($_POST['name']) < 2) {
                return ['error' => 'Имя должно быть минимум 2 символа'];
            }
            
            // Дублирование форматирования
            $userData = [
                'name' => ucfirst(trim($_POST['name'])),
                'email' => strtolower(trim($_POST['email'])),
                'created_at' => date('Y-m-d H:i:s')
            ];
            
            // Создание пользователя
            return $this->userModel->create($userData);
        }
        
        public function update($id) {
            // Та же валидация - дублирование!
            if (empty($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
                return ['error' => 'Некорректный email'];
            }
            if (empty($_POST['name']) || strlen($_POST['name']) < 2) {
                return ['error' => 'Имя должно быть минимум 2 символа'];
            }
            
            // То же форматирование - дублирование!
            $userData = [
                'name' => ucfirst(trim($_POST['name'])),
                'email' => strtolower(trim($_POST['email'])),
                'updated_at' => date('Y-m-d H:i:s')
            ];
            
            return $this->userModel->update($id, $userData);
        }
    }
    
    class ProfileController {
        public function updateProfile($userId) {
            // Снова та же валидация - третье дублирование!
            if (empty($_POST['email']) || !filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
                return ['error' => 'Некорректный email'];
            }
            if (empty($_POST['name']) || strlen($_POST['name']) < 2) {
                return ['error' => 'Имя должно быть минимум 2 символа'];
            }
            
            // И снова то же форматирование!
            $userData = [
                'name' => ucfirst(trim($_POST['name'])),
                'email' => strtolower(trim($_POST['email']))
            ];
            
            return $this->userModel->update($userId, $userData);
        }
    }
    
    
    // ✅ ХОРОШО: Следование принципу DRY
    
    // Выносим валидацию в отдельный класс
    class UserValidator {
        public static function validate($data) {
            $errors = [];
            
            if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
                $errors[] = 'Некорректный email';
            }
            
            if (empty($data['name']) || strlen($data['name']) < 2) {
                $errors[] = 'Имя должно быть минимум 2 символа';
            }
            
            return $errors;
        }
    }
    
    // Выносим форматирование в отдельный класс
    class UserFormatter {
        public static function format($data) {
            return [
                'name' => ucfirst(trim($data['name'])),
                'email' => strtolower(trim($data['email']))
            ];
        }
    }
    
    // Теперь контроллеры используют общую логику
    class UserController {
        public function create() {
            $errors = UserValidator::validate($_POST);
            if (!empty($errors)) {
                return ['errors' => $errors];
            }
            
            $userData = UserFormatter::format($_POST);
            $userData['created_at'] = date('Y-m-d H:i:s');
            
            return $this->userModel->create($userData);
        }
        
        public function update($id) {
            $errors = UserValidator::validate($_POST);
            if (!empty($errors)) {
                return ['errors' => $errors];
            }
            
            $userData = UserFormatter::format($_POST);
            $userData['updated_at'] = date('Y-m-d H:i:s');
            
            return $this->userModel->update($id, $userData);
        }
    }
    
    class ProfileController {
        public function updateProfile($userId) {
            $errors = UserValidator::validate($_POST);
            if (!empty($errors)) {
                return ['errors' => $errors];
            }
            
            $userData = UserFormatter::format($_POST);
            
            return $this->userModel->update($userId, $userData);
        }
    }
    
    // Еще лучше - создать сервис-класс
    class UserService {
        public function processUserData($data) {
            $errors = UserValidator::validate($data);
            if (!empty($errors)) {
                return ['success' => false, 'errors' => $errors];
            }
            
            return ['success' => true, 'data' => UserFormatter::format($data)];
        }
    }
    
    // Теперь контроллеры становятся еще проще
    class UserController {
        private $userService;
        
        public function __construct() {
            $this->userService = new UserService();
        }
        
        public function create() {
            $result = $this->userService->processUserData($_POST);
            if (!$result['success']) {
                return $result;
            }
            
            $userData = $result['data'];
            $userData['created_at'] = date('Y-m-d H:i:s');
            
            return $this->userModel->create($userData);
        }
    }

KISS (Keep It Simple, Stupid)

Проблемы:

  • Контроллеры становятся сложными из-за смешивания ответственностей

  • Модели перегружены разнородной функциональностью

Решения:

  • Контроллеры простые — только принимает запрос и возвращает ответ

  • Каждый класс решает одну простую задачу

  • Модель содержит только данные и простую логику их обработки

  • Сервис координирует простые действия

Преимущества соблюдения:

  • Легче понимать код - простые решения быстрее осваиваются новыми разработчиками

  • Быстрее находить ошибки - в простом коде баги более очевидны

  • Проще добавлять функциональность - нет необходимости разбираться в сложной архитектуре

  • Меньше времени на отладку - простые компоненты легче тестировать и исправлять

  • Снижение когнитивной нагрузки - разработчик может сосредоточиться на бизнес-логике

  • Ускорение разработки - простые решения реализуются быстрее сложных

  • Меньше места для ошибок - чем проще код, тем меньше вероятность багов

  • Легче поддерживать - простую систему проще модифицировать и расширять

  • Лучшая производительность - простые решения часто работают быстрее

  • Упрощение тестирования - простые функции легче покрыть тестами

  • Снижение стоимости разработки - меньше времени на написание и поддержку кода

  • Повышение надежности - простые системы более стабильны и предсказуемы

// ❌ ПЛОХО: Нарушение принципа KISS
// Сложный контроллер смешивает много ответственностей
class ProductController {
    public function create() {
        // Валидация
        if (empty($_POST['name']) || strlen($_POST['name']) < 3) {
            return ['error' => 'Название должно быть минимум 3 символа'];
        }
        if (empty($_POST['price']) || !is_numeric($_POST['price']) || $_POST['price'] <= 0) {
            return ['error' => 'Некорректная цена'];
        }
        
        // Обработка данных
        $name = ucfirst(trim($_POST['name']));
        $price = round(floatval($_POST['price']), 2);
        $slug = strtolower(str_replace(' ', '-', $name));
        
        // Проверка уникальности
        $existing = $this->db->query("SELECT id FROM products WHERE slug = ?", [$slug]);
        if ($existing) {
            $slug .= '-' . time();
        }
        
        // Сохранение
        $id = $this->db->insert("INSERT INTO products (name, price, slug, created_at) VALUES (?, ?, ?, NOW())", 
            [$name, $price, $slug]);
        
        // Отправка уведомления администратору
        mail('admin@site.com', 'Новый товар', "Создан товар: $name, цена: $price");
        
        // Логирование
        error_log("Product created: $name ($id)");
        
        return ['success' => true, 'id' => $id];
    }
}

// Перегруженная модель
class Product {
    public $id, $name, $price, $slug;
    
    // Работа с БД
    public function save() {
        return $this->db->insert("INSERT INTO products...");
    }
    
    // Валидация
    public function isValid() {
        return !empty($this->name) && $this->price > 0;
    }
    
    // Форматирование
    public function formatPrice() {
        return '$' . number_format($this->price, 2);
    }
    
    // Генерация slug
    public function generateSlug() {
        return strtolower(str_replace(' ', '-', $this->name));
    }
    
    // Отправка уведомлений
    public function notifyAdmin() {
        mail('admin@site.com', 'Товар изменен', "Товар {$this->name} обновлен");
    }
}


// ✅ ХОРОШО: Следование принципу KISS
// Простой контроллер с одной задачей
class ProductController {
    private $productService;
    
    public function __construct() {
        $this->productService = new ProductService();
    }
    
    public function create() {
        try {
            $product = $this->productService->create($_POST);
            return ['success' => true, 'id' => $product->id];
        } catch (Exception $e) {
            return ['error' => $e->getMessage()];
        }
    }
}

// Простой сервис координирует действия
class ProductService {
    private $validator;
    private $repository;
    private $notifier;
    
    public function __construct() {
        $this->validator = new ProductValidator();
        $this->repository = new ProductRepository();
        $this->notifier = new ProductNotifier();
    }
    
    public function create($data) {
        $this->validator->validate($data);
        
        $product = new Product($data);
        $product->id = $this->repository->save($product);
        
        $this->notifier->notifyCreated($product);
        
        return $product;
    }
}

// Каждый класс решает одну простую задачу
class ProductValidator {
    public function validate($data) {
        if (empty($data['name']) || strlen($data['name']) < 3) {
            throw new Exception('Название должно быть минимум 3 символа');
        }
        if (empty($data['price']) || $data['price'] <= 0) {
            throw new Exception('Некорректная цена');
        }
    }
}

class ProductRepository {
    public function save(Product $product) {
        $sql = "INSERT INTO products (name, price, slug) VALUES (?, ?, ?)";
        return $this->db->insert($sql, [$product->name, $product->price, $product->slug]);
    }
}

class ProductNotifier {
    public function notifyCreated(Product $product) {
        mail('admin@site.com', 'Новый товар', "Создан: {$product->name}");
    }
}

// Простая модель только с данными
class Product {
    public $id, $name, $price, $slug;
    
    public function __construct($data) {
        $this->name = ucfirst(trim($data['name']));
        $this->price = round(floatval($data['price']), 2);
        $this->slug = $this->createSlug($this->name);
    }
    
    private function createSlug($name) {
        return strtolower(str_replace(' ', '-', $name));
    }
}

Нарушения принципов GRASP

GRASP (General Responsibility Assignment Software Patterns) - это фундаментальные принципы распределения ответственности в объектно-ориентированном дизайне. Чистый MVC систематически нарушает большинство из них

Information Expert (Информационный эксперт)

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

Нарушение в MVC: Контроллеры выполняют операции с данными, которыми не владеют.

// ❌ Плохо - MVC нарушение
class UserController {
    public function calculateDiscount($userId) {
        $user = User::find($userId);
        $orders = Order::where('user_id', $userId)->get();
        
        // Контроллер вычисляет скидку, не имея информации о бизнес-правилах
        $totalSpent = $orders->sum('total');
        if ($totalSpent > 10000) {
            return 0.15; // 15% скидка
        } elseif ($totalSpent > 5000) {
            return 0.10; // 10% скидка
        }
        return 0;
    }
}

// ✅ Хорошо - Information Expert соблюден
class User {
    public function calculateDiscount(): float {
        $totalSpent = $this->orders->sum('total');
        return $this->discountStrategy->calculate($totalSpent);
    }
}

Creator (Создатель)

Принцип: Класс A должен создавать экземпляры класса B, если A содержит B, агрегирует B, или имеет данные для инициализации B.

Нарушение в MVC: Контроллеры создают объекты без логических оснований.

// ❌ Плохо - MVC нарушение
class OrderController {
    public function create(Request $request) {
        $order = new Order(); // Почему контроллер создает заказ?
        $order->user_id = $request->user_id;
        
        foreach ($request->items as $item) {
            $orderItem = new OrderItem(); // И элементы заказа?
            $orderItem->product_id = $item['product_id'];
            $orderItem->quantity = $item['quantity'];
            $order->items()->save($orderItem);
        }
        
        $payment = new Payment(); // И платеж?
        $payment->order_id = $order->id;
        $payment->save();
    }
}

// ✅ Хорошо - Creator соблюден
class Order {
    public function addItem(Product $product, int $quantity): OrderItem {
        // Заказ создает свои элементы - логично
        return new OrderItem($this, $product, $quantity);
    }
    
    public function createPayment(PaymentMethod $method): Payment {
        // Заказ создает свой платеж - логично
        return new Payment($this, $method, $this->getTotal());
    }
}

Controller (Контроллер в смысле GRASP)

Принцип: Не-UI объект должен обрабатывать системные события и координировать работу.

Нарушение в MVC: HTTP-контроллер смешивается с доменным контроллером.

// ❌ Плохо - смешивание HTTP и доменной логики
class PaymentController {
    public function processPayment(Request $request) {
        // HTTP-логика смешана с бизнес-логикой
        $order = Order::find($request->order_id);
        
        // Валидация HTTP-запроса
        if (!$request->has('payment_method')) {
            return response()->json(['error' => 'Payment method required'], 400);
        }
        
        // Бизнес-логика обработки платежа
        if ($order->status !== 'pending') {
            return response()->json(['error' => 'Order not pending'], 400);
        }
        
        // Вызов внешнего API
        $paymentGateway = new PaymentGateway();
        $result = $paymentGateway->charge($order->total, $request->payment_method);
        
        if ($result->success) {
            $order->status = 'paid';
            $order->save();
            
            // Отправка email
            Mail::send('payment_success', ['order' => $order]);
        }
    }
}

// ✅ Хорошо - разделение ответственностей
class PaymentController { // HTTP-контроллер
    public function processPayment(Request $request) {
        $dto = ProcessPaymentDTO::fromRequest($request);
        $result = $this->paymentService->processPayment($dto);
        return PaymentResource::make($result);
    }
}

class PaymentService { // Доменный контроллер
    public function processPayment(ProcessPaymentDTO $dto): PaymentResult {
        // Только бизнес-логика
        $order = $this->orderRepository->find($dto->orderId);
        $this->paymentValidator->validate($order, $dto);
        
        return $this->paymentProcessor->process($order, $dto->paymentMethod);
    }
}

Low Coupling (Слабая связанность)

Принцип: Классы должны иметь минимальную зависимость друг от друга.

Нарушение в MVC: Контроллеры тесно связаны с моделями, внешними сервисами и представлениями.

// ❌ Плохо - высокая связанность
class UserController {
    public function updateProfile(Request $request) {
        $user = User::find($request->user_id); // Связь с Eloquent
        
        // Связь с валидационной логикой
        if (!filter_var($request->email, FILTER_VALIDATE_EMAIL)) {
            return redirect()->back()->withErrors(['email' => 'Invalid email']);
        }
        
        // Связь с файловой системой
        if ($request->hasFile('avatar')) {
            $path = $request->file('avatar')->store('avatars', 's3');
            $user->avatar = $path;
        }
        
        // Связь с внешним API
        $geocoder = new GoogleGeocoder();
        $coordinates = $geocoder->geocode($request->address);
        
        // Связь с email-сервисом
        Mail::to($user->email)->send(new ProfileUpdatedMail($user));
        
        // Связь с кешем
        Cache::forget("user.{$user->id}");
        
        return view('profile.updated', compact('user')); // Связь с представлением
    }
}

// ✅ Хорошо - слабая связанность через интерфейсы
class UserController {
    public function __construct(
        private UserServiceInterface $userService
    ) {}
    
    public function updateProfile(UpdateProfileRequest $request) {
        $dto = UpdateProfileDTO::fromRequest($request);
        $user = $this->userService->updateProfile($dto);
        return UserResource::make($user);
    }
}

High Cohesion (Высокая связность)

Принцип: Элементы класса должны работать вместе для достижения общей цели.

Нарушение в MVC: Контроллеры содержат разнородную функциональность.

// ❌ Плохо - низкая связность
class UserController {
    public function register(Request $request) { /* регистрация */ }
    public function login(Request $request) { /* аутентификация */ }
    public function updateProfile(Request $request) { /* обновление профиля */ }
    public function uploadAvatar(Request $request) { /* загрузка файлов */ }
    public function sendEmail(Request $request) { /* отправка email */ }
    public function generateReport(Request $request) { /* генерация отчетов */ }
    public function exportData(Request $request) { /* экспорт данных */ }
    public function calculateStatistics() { /* расчет статистики */ }
    // Один контроллер делает все!
}

// ✅ Хорошо - высокая связность
class UserRegistrationController {
    public function register(Request $request) { /* только регистрация */ }
}

class UserAuthenticationController {
    public function login(Request $request) { /* только аутентификация */ }
}

class UserProfileController {
    public function show(Request $request) { /* показ профиля */ }
    public function update(Request $request) { /* обновление профиля */ }
}

Polymorphism (Полиморфизм)

Принцип: Используйте полиморфные операции вместо условий типа.

Нарушение в MVC: Контроллеры содержат множество условий вместо полиморфизма.

// ❌ Плохо - условия вместо полиморфизма
class PaymentController {
    public function process(Request $request) {
        $paymentType = $request->payment_type;
        
        if ($paymentType === 'credit_card') {
            $gateway = new CreditCardGateway();
            $result = $gateway->charge($request->amount, $request->card_data);
        } elseif ($paymentType === 'paypal') {
            $gateway = new PayPalGateway();
            $result = $gateway->pay($request->amount, $request->paypal_token);
        } elseif ($paymentType === 'crypto') {
            $gateway = new CryptoGateway();
            $result = $gateway->transfer($request->amount, $request->wallet_address);
        }
        
        // При добавлении нового типа нужно модифицировать контроллер
    }
}

// ✅ Хорошо - полиморфизм
interface PaymentGatewayInterface {
    public function process(PaymentData $data): PaymentResult;
}

class PaymentController {
    public function process(Request $request) {
        $gateway = $this->paymentGatewayFactory->create($request->payment_type);
        $data = PaymentData::fromRequest($request);
        return $gateway->process($data); // Полиморфный вызов
    }
}

Pure Fabrication (Чистая выдумка)

Принцип: Создавайте искусственные классы для достижения низкой связанности и высокой связности.

Нарушение в MVC: Отсутствие промежуточных слоев приводит к прямым связям.

// ❌ Плохо - прямые связи без промежуточных слоев
class UserController {
    public function register(Request $request) {
        // Прямая работа с базой данных
        $user = new User();
        $user->email = $request->email;
        $user->save();
        
        // Прямая работа с внешним API
        $emailService = new SendGridEmailService();
        $emailService->sendWelcomeEmail($user->email);
        
        // Прямая работа с файловой системой
        Storage::disk('s3')->put("users/{$user->id}/avatar", $request->file('avatar'));
    }
}

// ✅ Хорошо - Pure Fabrication через сервисные слои
class UserRegistrationService { // Искусственный класс для координации
    public function __construct(
        private UserRepositoryInterface $userRepository,
        private EmailServiceInterface $emailService,
        private FileStorageInterface $fileStorage
    ) {}
    
    public function register(RegisterUserDTO $dto): User {
        $user = $this->userRepository->create($dto);
        $this->emailService->sendWelcomeEmail($user);
        
        if ($dto->avatar) {
            $this->fileStorage->storeAvatar($user, $dto->avatar);
        }
        
        return $user;
    }
}

Indirection (Перенаправление)

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

Нарушение в MVC: Контроллеры напрямую обращаются к моделям и внешним сервисам.

// ❌ Плохо - прямые связи
class OrderController {
    public function create(Request $request) {
        $order = Order::create($request->all()); // Прямая связь с моделью
        
        // Прямая связь с внешним сервисом
        $inventory = new InventoryService();
        $inventory->reserveItems($order->items);
        
        // Прямая связь с платежным шлюзом
        $paymentGateway = new StripeGateway();
        $paymentGateway->createPaymentIntent($order->total);
    }
}

// ✅ Хорошо - Indirection через интерфейсы и адаптеры
class OrderController {
    public function __construct(
        private OrderServiceInterface $orderService // Непрямая связь
    ) {}
    
    public function create(CreateOrderRequest $request) {
        $dto = CreateOrderDTO::fromRequest($request);
        return $this->orderService->createOrder($dto); // Делегирование
    }
}

Что говорит сообщество?

Martin Fowler

Parts of classic MVC don’t really make sense for rich clients these days.” - Он указывает, что классическая MVC-архитектура устарела и не подходит для современных клиент-приложений.

How MVC Frameworks Taught Us Bad Habits — Part 2 How Monoliths are Born

Изменения в проекте приводят к росту контроллеров и моделей, а именно бизнес-слой становится «гигантскими», практически целиком захватывая логику приложения

A Journey Through Anti-Patterns and Code Smells

“Write business logic in view templates” — бизнес‑логика в шаблонах представления ухудшает читабельность.

“Set view variables everywhere” — контроллеры растут, а точки установки данных хаотично разбросаны по коду, что ухудшает поддержку и тестируемость.

MVC Is Dead

Now I’m not convinced that MVC was ever a great idea for full stack apps - Почти всё выполняется в контроллерах (mvC), модели и представления сводятся к минимуму, и от MVC фактически остаётся только буква C

MVC is evil, nowadays

“Scalability is not feasible when large percentages of your additions constantly require modifications to your existing system.” Автор утверждает, что MVC плохо масштабируется, поскольку новые функции почти всегда требуют изменений существующих компонентов и нарушают принцип выделения ответственности

MVC/MVP/MVVM? No Thanks

«MVC: Ideal for small scale projects only.» Высказывается сомнение в применении любых MVx-паттернов в масштабных системах — они не растут вместе с проектом и требуют перехода на более сложные альтернативы

Argument — It is time to stop making MVC applications

Контроллер всё чаще используется как точка внедрения бизнес‑логики и связки с представлением, а не просто для обработки UI, превращаясь “в MVC Video Streaming или MVC Warehouse management application.” То есть вместо приложения строится «MVC-приложение», где бизнес‑логика тесно переплетена с контроллерами

MVC – a Problem or a Solution?

Then, someone noted that ‘thin controller’ is not always the best approach. They thus created the rule of fat controller, thin model… we made poor MVC give birth to HMVC, MVA, MVP, MVVM… - Говориться о том что контролеры стают "жирными" что усложняет понимания кода


Заключение

Использование MVC — это удобная отправная точка для небольших и средних проектов. Он прост, широко понятен и поддерживается большинством фреймворков. Однако по мере роста продукта MVC становится архитектурным тормозом: он нарушает принципы модульности, затрудняет тестирование и ведёт к плотной связанности компонентов.

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

  • контроллеры нарушают Single Responsibility Principle, совмещая ответственность за маршрутизацию, бизнес-логику и работу с представлением;

  • изменения в логике требуют модификации существующих классов, нарушая Open/Closed Principle;

  • слои начинают пересекаться, и Separation of Concerns размывается;

  • отсутствует гибкая инверсия зависимостей — контроллеры зависят от конкретных реализаций, что нарушает Dependency Inversion Principle.

Все эти проблемы делают MVC непригодным для устойчивой и масштабируемой архитектуры. Для зрелых приложений необходимы более строгие архитектурные подходы — такие как Clean Architecture, Hexagonal, CQRS, где строго разделены слои, соблюдаются зависимости и обеспечивается высокая тестируемость и расширяемость.

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


  1. Emelian
    01.07.2025 12:56

    Нарушение принцыпа

    Вообще-то, принцИпа.


    1. dykyi_roman Автор
      01.07.2025 12:56

      Спасибо! Поправил


  1. Emelian
    01.07.2025 12:56

    MVC (Model–View–Controller) – это архитектурный шаблон, предложенный Тригве Ренскаугом в конце 1970., который разделяет код на три части: модель (данные и бизнес-логика), представление (UI) и контроллер (обработчик запросов).

    Я бы выделил, для оконных приложений, на C++/WTL, другие принципы:

    1. Менеджер потоков.

    2. Менеджер видов (дочерних окон)

    3. Менеджер событий (для программной и бизнес логики, в том числе).

    Посмотрим, чем SOLID может быть полезным для моих программ (пет-проектов на C++/WTL):

    S - Класс должен иметь только одну причину для изменения.

    Непонятная формулировка. Кем изменяется, своей программой или чужой?

    O - Классы должны быть открыты для расширения, но закрыты для модификации.

    То же самое. Для собственных пет-проектов это не актуально.

    L - Подклассы контроллеров меняют поведение базового класса.

    То же самое.

    I - Клиенты не должны зависеть от интерфейсов, которые они не используют.

    Согласен.

    D - Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

    Модули в С++ имеют неоднозначное толкование. Для классов может быть и так и не так.

    В общем, SOLID мне не нужен. Более подходит метод «здравого смысла». Ну, если рассуждать, как в анекдоте: «– Ненавижу Карузо! – О-о-о! А где вы его слушали? – Да, мне Мойша по телефону напел!».

    Пойдем дальше:

    DRY (Don't Repeat Yourself)

    Достаточно очевидно, как для метода «здравого смысла».

    KISS (Keep It Simple, Stupid)

    То же самое.

    GRASP (General Responsibility Assignment Software Patterns) :

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

    Достаточно очевидно.

    Creator: Класс A должен создавать экземпляры класса B, если A содержит B, агрегирует B, или имеет данные для инициализации B.

    То же самое.

    Controller (Контроллер в смысле GRASP): Не-UI объект должен обрабатывать системные события и координировать работу.

    Непонятно и не кажется важным.

    Low Coupling (Слабая связанность): Классы должны иметь минимальную зависимость друг от друга.

    High Cohesion (Высокая связность) Элементы класса должны работать вместе для достижения общей цели.

    Слишком общие формулировки, чтобы иметь неочевидный смысл.

    Polymorphism (Полиморфизм): Используйте полиморфные операции вместо условий типа.

    Чистый ООП?

    Pure Fabrication (Чистая выдумка): Создавайте искусственные классы для достижения низкой связанности и высокой связности.

    Без проекции на программную реализацию конкретной модели, не имеет явного смысла.

    Indirection (Перенаправление): Избегайте прямых связей между компонентами, используя промежуточные объекты.

    То же самое.

    Мне всегда было влом разбираться с новомодными принципами программирования. И вот появился повод, посмотреть на них хотя бы краем глаза. В итоге, остался при своем мнении: Самый надежный принцип: метод «здравого смысла». Пока он меня еще не подводил…


    1. SerafimArts
      01.07.2025 12:56

      Я бы выделил, для оконных приложений, на C++/WTL

      Плюсую. Реализация классического MVC в вебе технически невозможна (без сокетов, лонг-поллинга или других костылей).

      Серверное ПО (Symfony, Laravel, etc) в подавляющем большинстве случаев использует MVP архитектуру (если исключить специфику веба, вроде роутера и прочих).


  1. DExploN
    01.07.2025 12:56


    "Модель в MVC часто понимается как ORM-объект или ActiveRecord".

    Имхо, неверное заявление. Мне кажется, корень статьи идет от неверного понимания М. Скорее всего это было притянуто из-за того, что в Laravel (а примеры из него), модели - это объекты Active Record. Но модель Laravel - это не модель MVC. Как например фасад в Ларавел, не является паттерном "фасад", что подменяет понятия нормального фасада, и куча людей бегает и говорит что фасад - антипаттерн, на основе Ларавел.

    Например в Symfony никто не называет объекты ORM - моделью. И не говорят, что entity это M из MVC.

    Классический MVC не предлагает явного и самодостаточного слоя для сложной, чистой бизнес-логики

    Модель - это слой логики отделенный от слоя транспорта и представления, всё и ничего более. Дробить саму Модель на другие слои (домен, апилекейшен, инфра), сложные структуры - да пожалуйста.

    Просто ранее и этого не было, писали все в одном файле образно article_page.php, где доставались данные из $_GET, там же ходили в БД, там же формировали html.
    И то, что теперь кажется Вам дефолтом, который предоставляет любой фрейморк - это и есть MVC. Ну если уж совсем не ушли в Fat Controller.

    В общем,имхо, статья красивая, объемная, но понимание Model в MVC неверное. У вас из коробки фреймворка MVC - контроллер, View - рендеррер ,Model - логика. А если копнуть чуть глубже, то скорее даже MVP, потому что модели не ходят в view как в классической схеме. Так что, вы используете этот паттерн постоянно на автомате, а уже на основе него другие архитектуры и подходы используете.




  1. Jony2Good
    01.07.2025 12:56

    В чистом MVC буква М — это не просто ORM, а вся бизнес-логика и доступ к данным, то есть:
    - бизнес-правила, работа с базой, валидации, доменные сервисы, агрегаты и т.п. Это не просто сущности (типа User или Product) в Laravel.

    Laravel называет "моделью" то, что по DDD называется Entity или Active Record. Это сделано для облегчения разработки и путает неокрепшие умы. В более чистой архитектуре (например, DDD + Hexagonal) Laravel модель будет разделена на:

    1. Entity (сущность),

    2. Repository (доступ к данным),

    3. Service (бизнес-логика),

    4. Value Object и пр.

    Поэтому согласен с одним из комментаторов, что автор чуть в другую сторону ушёл в понимании MVC. Уместнее данную статью назвать "Проблемы реализации паттерна MVC на примере Laravel фреймворка"