За последние 24 часа нас посетили 54447 программистов и 1778 роботов. Сейчас ищут 1008 программистов ...

Помогите понять MVC

Тема в разделе "PHP для новичков", создана пользователем shareware, 28 ноя 2021.

Метки:
  1. shareware

    shareware Новичок

    С нами с:
    26 июл 2019
    Сообщения:
    20
    Симпатии:
    0
    Работал я с ларавель..и с Yii2..и с вьюшками..и с роутингом..все равно не понял за MVC

    Подскажите, вот такой пример можно назвать моделью ?


    PHP:
    1. class Model extends DBConnect{
    2.     private $dbconnect,
    3.             $tablename;
    4.  
    5.     public function __construct($tablename)
    6.     {
    7.         parent::__construct();
    8.         $this->dbconnect = $this->connect();
    9.         $this->tablename = $this->dbconnect->real_escape_string($tablename);
    10.     }
    11.  
    12.     private function checkExistsTable($tablename): Bool
    13.     {
    14.         $query = $this->dbconnect->query("SHOW TABLES LIKE '{$tablename}'");
    15.         if ($query->num_rows > 0)
    16.         {
    17.             return true;
    18.         }else{
    19.             return false;
    20.         }
    21.     }
    22.  
    23.     public function getAll(): Array
    24.     {
    25.         $tablename = $this->checkExistsTable($this->tablename);
    26.         if ($tablename)
    27.         {
    28.             $query = $this->dbconnect->query("SELECT * FROM {$this->tablename}");
    29.             $result = $query->fetch_array();
    30.             return [
    31.                 'error'=>false,
    32.                 'msg_error'=>false,
    33.                 'content'=>$result
    34.             ];
    35.  
    36.         }else{
    37.             return [
    38.                 'error'=>true,
    39.                 'msg_error'=>'Указанная таблица не существует',
    40.                 'content'=>false
    41.             ];
    42.         }
    43.     }
    44.  
    45. }
     
  2. ADSoft

    ADSoft Старожил

    С нами с:
    12 мар 2007
    Сообщения:
    3.866
    Симпатии:
    753
    Адрес:
    Татарстан
    теоретически можно...
    только непонятно - зачем наследоваться от DBConnect, смысл модели в том что она может работать с данными... а откуда и как данные берутся - неважно... сейчас у вас из БД а потом раз надо чтоб данные с json файла брались ... все переписывать?

    и checkExistsTable - вы что в каждой модели будете определять ? Если уж на то пошло - это все должно быть в checkExistsTable
     
    SamyRed, MouseZver, shareware и ещё 1-му нравится это.
  3. twim32

    twim32 Активный пользователь

    С нами с:
    29 мар 2017
    Сообщения:
    275
    Симпатии:
    58
    Опять скрещиваете ежика и ёлку. Что общего у ёжика и у ёлки? Ежик живой, кушает яблоки, колит иголками, фырчит. Дерево тоже живое, оно растет, генерирует кислород, питается из земли. У обоих есть иголки. Но можем ли мы сказать что дерево ежик родственник дерева? Однозначно нет.

    Так же и с моделью и бд. Что делает дб? Может подключаться и работать с таблицами. А Модель всего лишь указывает БД что и откуда надо взять или обновить, так же знает типы значений и манипулирует ими. Зачем наследовать модель от базы данных? А вдруг вам придется брать значения из CSV, JSON? Создавать еще одну модель с тем же функционалом, только под каждый тип данных?

    В общем, на пальцах:
    1. Пользователь зашел по адресу http://example.com/?controller=CarController&method=showAllCars
    2. Сервер делает запрос к единой точке входа (index.php), где запускается все приложение. В том числе и роутер.
    3. При запуске роутера, он вызывает контроллер и его метод (если оба существуют). Если нет, то вызывается допустим ErrorController и его метод pageNotFound
    PHP:
    1. <?php
    2.  
    3. $controller_name = $_GET['controller'];
    4. $method_name = $_GET['method'];
    5.  
    6. if(
    7.     class_exists($controller_name) &&
    8.     $controller_name instanceof Controller::class &&
    9.     method_exists($controller_name, $method_name)
    10. ) {
    11.     $controller = new $controller_name;
    12.     $controller->$method_name();
    13. } else {
    14.     $controller = new ErrorController;
    15.     $controller->pageNotFound();
    16. }
    4. В методе контроллера (в данном случае это showAllCars()) мы можем делать всё что захотим: взять все автомобили, производителей, точки продаж и передать это во View. Ему не важно откуда это всё берётся и как сохраняется. Об этом знает только модель.
    5. Класс View находит темплейт, передает параметры и отдает представление.
    6. Если больше запросов нет, бд сама разрывает подключение.

    Про модели и дб
    1. Модель обращается к базе данных и говорит : дай-ка мне все записи из этой таблицы)
    2. Бд подключается (если ранее не подключалась), выполняет запрос и отдает ответ обратно в модель.
    3. Модель получает ответ от БД (это может быть array/bool/int, в зависимости от запроса) и обрабатывает его.
    - Если был вызван метод модели select, то получает от бд массив записей, оборачивает его в собственные экземпляры и отдает дальше.
    - Если был вызван create/update/delete, то обновляет свои парметры и отдает ответ дальше.

    Поэтому нужно сделать абстрактный класс модели, где будет описаны все действия (см. в предыдущих ответах). А потом наследовать от нее все модели, указывая в каждой наименование таблицы с которой она работает. И вам не придется заниматься копипастой методов из модели в модель. И у каждой модели может быть свои отдельные методы.
    Например :
    - Car::getAllWithColor('green'): array; // взять все зеленые машины
    - Manager::getActives(): array; //взять всех свободных манагеров
    --- Добавлено ---
    checkExistsTable - метод класса бд, не модели. Проще говоря не надо городить целую модель чтобы проверить наличие таблицы.
    DB::tableExists('whatever_table') : bool // true/false
    DB::dropTable('whatever_table') : bool // true/false

    И проверять каждый раз существование таблицы - лишняя трата времени. SQL и так выдаст ошибку, если таблицы нет.
     
    shareware нравится это.
  4. Sviridov

    Sviridov Новичок

    С нами с:
    22 сен 2021
    Сообщения:
    24
    Симпатии:
    4
    Controller - Логика. В основном здесь условия, взаимодействие с моделями и вызов рендера через view. Здесь никаких запросов к бд и уж тем более html кода.
    Model - работа с данными, в большинстве случаев работа с бд. здесь никакой логики, а уж тем более html кода, только код для работы с базой.
    View - шаблон, здесь ни логики ни работы с данными нет. я допускаю в своих проектах максимум проверки типа if и т.п.

    Посмотрите на проекты под CodeIgniter 3, Opencart.
    По поводу писать свой мвс двиг или брать фреймворк - я выбрал первое, потому что написал свой мвс и съев на нем собаку (попутно изучая фреймворки, CMS-ки и т.п.) я стал больше понимать не только в MVC, но и в целом я хорошо улучшил свой скилл.
    --- Добавлено ---
    Это я бы не назвал моделью. Вместо ошибки, верните пустой массив / false, а в контроллере сделайте проверку if(empty($result)) и выводите ошибку. Модель только работает с данными, никакой логики там быть не может.
     
  5. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.861
    Симпатии:
    657
    ЧЁ, правда? При работе с данными логика не нужна? :eek:
    --- Добавлено ---
    И когда говорят про логику в модели, этот термин используют в более широком смысле, чем его понимаете вы ;)
     
  6. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.818
    Симпатии:
    1.333
    Адрес:
    Лень
    Всмысле ?? я в моделе что хочу то и ворочу, а контроллер получается на столько минимальным, что просто устанавливает свои титлы названия страницы и запускает рендеринг. Какого только с БД работа... ?
    --- Добавлено ---
    PHP:
    1. final class MainController extends AbstractController
    2. {
    3.     public function index(): CommitRepository
    4.     {
    5.         $this -> title( [ 'Главная', 'Не главная' ], true );
    6.        
    7.         return $this -> render( 'index' );
    8.     }
    9. }
     
  7. Sviridov

    Sviridov Новичок

    С нами с:
    22 сен 2021
    Сообщения:
    24
    Симпатии:
    4
    Фильтрация , валидация происходит обычно в контроллере. В модель передаются проверенные данные. Если что то не проходит проверку, до модели это не доходит. Вот я о чем. Я думаю что довольно понятно изложил суть MVC как её понимаю я.
    --- Добавлено ---
    Я предпочитаю использовать MVC так как я его описал.
    Код (Text):
    1. https://lectureswww.readthedocs.io/_images/mvc.svg
     
  8. alexphp

    alexphp Активный пользователь

    С нами с:
    5 дек 2019
    Сообщения:
    98
    Симпатии:
    12
    Кто сказал, что в MVC view не может брать данные напрямую из model, минуя controller?
     
  9. Vanchot

    Vanchot Активный пользователь

    С нами с:
    23 мар 2019
    Сообщения:
    104
    Симпатии:
    19
    Адрес:
    Ахерон (LV-426)
    В аббревиатуре MVC "M" первая! Далее view, и потом только controller. Вы всё попутали.
     
  10. alexphp

    alexphp Активный пользователь

    С нами с:
    5 дек 2019
    Сообщения:
    98
    Симпатии:
    12
    Кому ответ? Мне? Автору темы?
     
  11. Sviridov

    Sviridov Новичок

    С нами с:
    22 сен 2021
    Сообщения:
    24
    Симпатии:
    4
    Прочитайте документации к MVC фреймворкам.
     
  12. Vanchot

    Vanchot Активный пользователь

    С нами с:
    23 мар 2019
    Сообщения:
    104
    Симпатии:
    19
    Адрес:
    Ахерон (LV-426)
    Мне не интересно. Работал с большими проектами в крупной компании. Сами читайте. Автор пишет, что тоже работал с фреймворками на принципах MVC, но ничего не понял. Вам хочется поумничать, ну так вперёд, объясняйте дальше и очень подробно, не ленитесь.
     
  13. Vanchot

    Vanchot Активный пользователь

    С нами с:
    23 мар 2019
    Сообщения:
    104
    Симпатии:
    19
    Адрес:
    Ахерон (LV-426)
    Это про MVVM?
     
  14. alexphp

    alexphp Активный пользователь

    С нами с:
    5 дек 2019
    Сообщения:
    98
    Симпатии:
    12
    Зачем MVVM? Это все тот же MVC.
    Просто необходим ли C, чтобы V взял данные из M? Что, V напрямую из M брать не может?
    А что будет делать C? А C будет выполнять какую-то другую функцию, а не посредничество от M к V.
     
  15. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.861
    Симпатии:
    657
    Какая-то важная часть данных окажется недоступна и все. Будете нагружать вьюшку всякой хренью для обработки ошибок? Даже исключения, возникающие при выполнении вьюшки, значительно сложнее обрабатывать.
     
    alexphp нравится это.
  16. twim32

    twim32 Активный пользователь

    С нами с:
    29 мар 2017
    Сообщения:
    275
    Симпатии:
    58
    А что будете делать вы, когда вам скажут написать тесты?

    С должны брать М и передавать во V. Неважно как, через репозитории (репозитАрии) или из самой модели.
    Если должна выполнится какая-либо бизнес логика, то она должна быть вынесена в Сервис. В контроллере ловим исключения и показываем результат. Таким образом мы можем спокойно тестировать сервисы и запускать их в терминале.
     
    alexphp нравится это.
  17. Vanchot

    Vanchot Активный пользователь

    С нами с:
    23 мар 2019
    Сообщения:
    104
    Симпатии:
    19
    Адрес:
    Ахерон (LV-426)
    @alexphp, дайте пример, где это точно нужно, где без этого не обойтись. View по идее должен получать массив данных, которые нужно расставить по местам. Логика там может быть, но только для создания конечного результата. Например, скрыть блок такой-то от определенных клиентов, кому-то показать, или вывести таблицу особым образом. Если View чего-то не хватает, и он запрашивет дополнительные данные из Model, то что-то сделано не так, повозка начала управлять лошадью.
    И тесты, конечно. Без них только что-то очень маленькое делается, "на коленке".
    Кстати, если на выходе только JSON, View вообще не нужен.
     
    #42 Vanchot, 2 дек 2021
    Последнее редактирование: 2 дек 2021
    alexphp нравится это.
  18. Vanchot

    Vanchot Активный пользователь

    С нами с:
    23 мар 2019
    Сообщения:
    104
    Симпатии:
    19
    Адрес:
    Ахерон (LV-426)
    С берёт данные через M (а M бывает по 50 штук в папке, по 40 тысяч строк кода каждая, потому нужно ещё с умом выбирать необходимые M), и в зависимости от правил роутера возвращает JSON, либо XML, либо подключает V для рендера.
     
    alexphp нравится это.
  19. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.818
    Симпатии:
    1.333
    Адрес:
    Лень
    Мой Viewer служит лишь как раскладчик/сортировщик данных. Ему неизвестны, не контроллер, не модели, не App с контейнером, он не знает что творится в приложении, кроме Request, Response и некоторых данных для шаблонов.
    Каких некоторых ? Путь к шаблонам, имя слоя стандартного ( default-template ), расширение шаблонов ( стандарт *.php ), title, head. Всё берется дейфолтом с конфига.
    PHP:
    1.     \View :: class => static function ( ContainerInterface $container )
    2.     {
    3.         $app = $container -> get( 'app' );
    4.      
    5.         $repository = $app -> repository;
    6.      
    7.         $viewer = new \Nouvu\Web\View\Viewer( $app -> request, $app -> response );
    8.      
    9.         foreach ( [ 'path', 'layout', 'extension', 'title', 'head' ] AS $name )
    10.         {
    11.             $viewer -> { 'set' . ucfirst ( $name ) }( $repository );
    12.         }
    13.      
    14.         return $viewer;
    15.     },
    PHP:
    1. <?php
    2.  
    3. declare ( strict_types = 1 );
    4.  
    5. namespace Nouvu\Web\View;
    6.  
    7. use Symfony\Component\HttpFoundation\{ Request, Response };
    8. use Nouvu\Web\View\Builder\Content;
    9. use Nouvu\Web\View\Repository\{ CommitRepository, HeadRepository, TitleRepository };
    10. use Nouvu\Web\Component\Config\Repository;
    11.  
    12. final class Viewer
    13. {
    14.     public HeadRepository $head;
    15.     public TitleRepository $title;
    16.  
    17.     private string $directory;
    18.     private string | null $layout = null;
    19.     private string $extension = '.php';
    20.  
    21.     public function __construct ( private Request $request, private Response $response )
    22.     {
    23.         $this -> head = new HeadRepository( [ 'list' => [], 'selected' => [] ] );
    24.      
    25.         $this -> title = new TitleRepository( [ 'list' => [], 'delimiter' => ' - ' ] );
    26.     }
    27.  
    28.     public function setPath( Repository $repository ): void
    29.     {
    30.         $path = $repository -> get( 'app.system.directory.view' ) . $repository -> get( 'config.theme' );
    31.      
    32.         $this -> directory = rtrim ( $path, '\\/' ) . DIRECTORY_SEPARATOR;
    33.     }
    34.  
    35.     public function setLayout( Repository $repository ): void
    36.     {
    37.         $this -> layout = $repository -> get( 'config.default_template' );
    38.     }
    39.  
    40.     public function setExtension( Repository $repository ): void
    41.     {
    42.         $this -> extension = $repository -> get( 'viewer.extension' );
    43.     }
    44.  
    45.     public function setHead( Repository $repository ): void
    46.     {
    47.         $this -> head -> add( 'list', $repository -> get( 'viewer.head' ) );
    48.     }
    49.  
    50.     public function setTitle( Repository $repository ): void
    51.     {
    52.         $this -> title -> set( $repository -> get( 'config.default_title' ) );
    53.     }
    54.  
    55.     public function render( CommitRepository $commit ): void
    56.     {
    57.         $commit -> get( 'layout' ) ?? $commit -> reset ( 'layout', $this -> layout );
    58.      
    59.         $commit -> reset ( 'commit', 'render' );
    60.      
    61.         $commit -> replace( 'content', 'container.content' );
    62.      
    63.         $commit -> replace( 'layout', 'container.layout' );
    64.     }
    65.  
    66.     public function redirect( CommitRepository $commit ): void
    67.     {
    68.         $commit -> reset ( 'commit', 'redirect' );
    69.      
    70.         $commit -> replace( 'path', 'container' );
    71.     }
    72.  
    73.     public function json( CommitRepository $commit ): void
    74.     {
    75.         $this -> render( $commit );
    76.      
    77.         $commit -> reset ( 'commit', 'json' );
    78.     }
    79.  
    80.     public function custom( CommitRepository $commit ): void
    81.     {
    82.         $commit -> reset ( 'commit', 'custom' );
    83.     }
    84.  
    85.     public function filter( CommitRepository $commit ): void
    86.     {
    87.         $commit -> set( array_filter ( $commit -> all() ) );
    88.     }
    89.  
    90.     public function filling( CommitRepository $commit ): void
    91.     {
    92.         foreach ( [ 'directory', 'layout', 'head', 'title', 'extension' ] AS $name )
    93.         {
    94.             $commit -> reset ( $name, $this -> {$name} );
    95.         }
    96.     }
    97.  
    98.     public function terminal( CommitRepository $commit ): void
    99.     {
    100.         $this -> filter( $commit );
    101.      
    102.         $this -> filling( $commit );
    103.      
    104.         $this -> send( $commit, new Terminal( $commit ) );
    105.     }
    106.  
    107.     private function send( CommitRepository $commit, Terminal $terminal ): void
    108.     {
    109.         match ( $commit -> getCommit() )
    110.         {
    111.             'render'    => $terminal -> contentResponse( $this -> response, new Content( $commit ) ),
    112.             'redirect'    => $terminal -> redirectResponse( $this -> response ),
    113.             'json'        => $terminal -> jsonResponse( $this -> response, new Content( $commit ) ),
    114.             'custom'    => $terminal -> customResponse( $this -> response ),
    115.         };
    116.      
    117.         $this -> response -> prepare( $this -> request );
    118.      
    119.         $this -> response -> send();
    120.     }
    121. }
    После сортировки, у него дергают терминал. От требуемого типа действия, собранная/отсортированная информация в CommitRepository, попадает в этот терминал для составления документа/ответа.

    PHP:
    1. <?php
    2.  
    3. declare ( strict_types = 1 );
    4.  
    5. namespace Nouvu\Web\View;
    6.  
    7. use Symfony\Component\HttpFoundation\Response;
    8. use Nouvu\Web\View\Builder\Content;
    9. use Nouvu\Web\View\Repository\CommitRepository;
    10.  
    11. class Terminal
    12. {
    13.     public function __construct ( private CommitRepository $commit )
    14.     {
    15.         // ...
    16.     }
    17.  
    18.     public function contentResponse( Response $response, Content $build ): void
    19.     {
    20.         $response -> headers -> set( 'Content-Type', 'text/html' );
    21.      
    22.         $build -> setContent( $response );
    23.     }
    24.  
    25.     public function redirectResponse( Response $response ): void
    26.     {
    27.         $response -> setStatusCode( Response :: HTTP_MOVED_PERMANENTLY );
    28.      
    29.         $response -> headers -> set( 'Location', $this -> commit -> getContainer() );
    30.     }
    31.  
    32.     public function jsonResponse( Response $response, Content $build ): void
    33.     {
    34.         $response -> headers -> set( 'Content-Type', 'application/json' );
    35.      
    36.         $build -> setContent( $response, function ( string $content ) use ( $build ): string
    37.         {
    38.             return json_encode ( [
    39.                 'response' => 'content',
    40.                 'content' => $content,
    41.                 'title' => $build -> getTitle(),
    42.             ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
    43.         } );
    44.     }
    45.  
    46.     public function customResponse( Response $response ): void
    47.     {
    48.         $closure = $this -> commit -> get( 'closure' );
    49.      
    50.         $closure( $response );
    51.     }
    52. }
    Кстати, в CommitRepository находится объект контроллера и так же запущенные модели.
    Терминал... В зависимости от типа запроса, тело документа так же отдельно обрабатывается в билдере Content и возращается обратно в терминал.
    Builder\Content вовсе не знает ничего о приложении, нету доступа к окружению, только CommitRepository через которого может взаимодействовать только с контроллером и моделями.
    PHP:
    1. <?php
    2.  
    3. declare ( strict_types = 1 );
    4.  
    5. namespace Nouvu\Web\View\Builder;
    6.  
    7. use Symfony\Component\HttpFoundation\Response;
    8. use Nouvu\Web\View\Repository\CommitRepository;
    9. use Nouvu\Web\View\Builder\ShortTag;
    10. use Stringable;
    11.  
    12. class Content
    13. {
    14.     private string $content = '';
    15.  
    16.     public function __construct ( private CommitRepository $commit )
    17.     {
    18.         // $this -> commit -> reset( 'content', '' );
    19.     }
    20.  
    21.     protected function replaceCode( string $template, string $content ): Stringable
    22.     {
    23.         return new ShortTag(
    24.             [
    25.                 $this,
    26.                 $this -> commit -> get( 'controller' ),
    27.                 ...$this -> commit -> get( 'models', [] )
    28.             ],
    29.             function ( array $matches ) use ( $template ): string
    30.             {
    31.                 $file = dirname ( $template ) . DIRECTORY_SEPARATOR . $matches[1];
    32.              
    33.                 if ( file_exists ( $file . $this -> commit -> get( 'extension' ) ) )
    34.                 {
    35.                     return $this -> getHtml( $file );
    36.                 }
    37.              
    38.                 return "<!-- Not found ({$matches[1]}) -->";
    39.             },
    40.             $content
    41.         );
    42.     }
    43.  
    44.     public function setContent( Response $response, callable | null $call = null ): void
    45.     {
    46.         foreach ( array_filter ( $this -> commit -> getContainer() ) AS $name )
    47.         {
    48.             $this -> content = $this -> getHtml( $this -> commit -> get( 'directory' ) . $name );
    49.         }
    50.      
    51.         if ( is_callable ( $call ) )
    52.         {
    53.             $response -> setContent( $call( $this -> getContent() ) );
    54.          
    55.             return;
    56.         }
    57.      
    58.         $response -> setContent( $this -> getContent() );
    59.     }
    60.  
    61.     public function getContent(): string
    62.     {
    63.         return $this -> content;
    64.     }
    65.  
    66.     public function getTitle(): string
    67.     {
    68.         return $this -> commit -> get( 'title' ) -> getResult();
    69.     }
    70.  
    71.     public function getHead( string ...$heads ): string
    72.     {
    73.         if ( func_num_args () > 0 )
    74.         {
    75.             $this -> commit -> get( 'head' ) -> add( 'selected', func_get_args (), true );
    76.         }
    77.      
    78.         return implode ( PHP_EOL . "\t", iterator_to_array ( $this -> commit -> get( 'head' ) -> getResult() ) );
    79.     }
    80.  
    81.     public function getHtml( string $name ): string
    82.     {
    83.         ob_start ();
    84.      
    85.         $closure = $this -> commit -> get( 'controller' )();
    86.      
    87.         $closure( $name );
    88.      
    89.         return ( string ) $this -> replaceCode( $name, ob_get_clean () );
    90.     }
    91. }
    Его работа заключается в наложении на слой шаблона требуемый контент, обработка "моих шорт кодов, которые дергают публичные методы с приставкой get, пример: {<{username}>} ищем метод getUsername у себя в Content, в контроллере, после в моделях".

    Но перед всем этим - дергается сначала контроллер и в нем, если логика требуется, запускаем нужные модели хоть 100500 раз.
     
    #44 MouseZver, 3 дек 2021
    Последнее редактирование: 3 дек 2021
    alexphp нравится это.
  20. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.818
    Симпатии:
    1.333
    Адрес:
    Лень
    Сами модели с контроллером имеют доступ к приложению.

    PHP:
    1. <?php
    2.  
    3. declare ( strict_types = 1 );
    4.  
    5. namespace Nouvu\Web\Http\Controllers;
    6.  
    7. use Symfony\Component\HttpFoundation\Response;
    8. use Symfony\Component\Security\Core\Security;
    9. use Symfony\Component\Security\Core\User\UserInterface;
    10. use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
    11. use Nouvu\Web\Foundation\{ Application, ApplicationTrait };
    12. use Nouvu\Web\View\Repository\CommitRepository;
    13.  
    14. class AbstractController
    15. {
    16.    use ApplicationTrait;
    17.  
    18.    public function __construct ( protected Application $app )
    19.    {
    20.    }
    21.  
    22.    ....
    23.  
    24.  
    25.  
    26.    protected function render( string $content = '', string | null $layout = null ): CommitRepository
    27.    {
    28.      if ( $this -> isAjax() )
    29.      {
    30.        return $this -> json( $content );
    31.      }
    32.    
    33.      $commit = $this -> getCommitInstance( compact ( 'content', 'layout' ) );
    34.    
    35.      $this -> app -> view -> render( $commit );
    36.    
    37.      return $commit;
    38.    }
    39.  
    40.    public function __invoke()
    41.    {
    42.      return $this -> app -> repository -> get( 'viewer.include' );
    43.    }
    44. }
    PHP:
    1. <?php
    2.  
    3. declare ( strict_types = 1 );
    4.  
    5. namespace Nouvu\Resources\Models;
    6.  
    7. use Nouvu\Web\Foundation\Application AS App;
    8. use Nouvu\Web\Component\Validator\Exception\ViolationsException;
    9.  
    10. class AbstractModel //implements ???...
    11. {
    12.     public function __construct ( protected App $app )
    13.     {
    14.      
    15.     }
    16. }
    Ну а по запросу приложения, запускается определенный контроллер и выполняет свои действия...

    PHP:
    1. <?php
    2.  
    3. declare ( strict_types = 1 );
    4.  
    5. namespace Nouvu\Resources\Controllers;
    6.  
    7. use Nouvu\Web\Http\Controllers\AbstractController;
    8. use Nouvu\Web\View\Repository\CommitRepository;
    9.  
    10. final class MainController extends AbstractController
    11. {
    12.     public function index(): CommitRepository
    13.     {
    14.         $this -> title( [ 'Главная', 'Не главная' ], true );
    15.      
    16.         return $this -> render( 'index' );
    17.     }
    18.  
    19.     public function welcome( $slug = null ): CommitRepository
    20.     {
    21.         $this -> title( [ 'Welcome' ] );
    22.      
    23.         return $this -> render( 'welcome' );
    24.         //return $this -> render( 'test/login/login', 'test/login/template' );
    25.     }
    26.  
    27.     public function err404(): CommitRepository
    28.     {
    29.         $this -> title( [ 'Страница не найдена' ], true );
    30.      
    31.         return $this -> render( 'error.404', 'error-template' );
    32.     }
    33.  
    34.     public function err500(): CommitRepository
    35.     {
    36.         $this -> title( [ 'Ошибка сервера' ], true );
    37.      
    38.         return $this -> render( 'error.500', 'error-template' );
    39.     }
    40.  
    41.     public function testError(): CommitRepository
    42.     {
    43.         require '1.php'; // Test error
    44.       // $this -> getModel( 'auth.test' );
    45.         return $this -> render( 'index' );
    46.     }
    47. }
    --- Добавлено ---
    HTML:
    1. <!DOCTYPE html>
    2. <html lang="{<{locale}>}">
    3.     <title>{<{title}>}</title>
    4.     {<{head=meta-charset|meta-viewport}>}
    5. </head>
    6.  
    7. {<{content}>}
    8.  
    9. </body>
    10. </html>
     
    alexphp нравится это.