Работал я с ларавель..и с Yii2..и с вьюшками..и с роутингом..все равно не понял за MVC Подскажите, вот такой пример можно назвать моделью ? PHP: class Model extends DBConnect{ private $dbconnect, $tablename; public function __construct($tablename) { parent::__construct(); $this->dbconnect = $this->connect(); $this->tablename = $this->dbconnect->real_escape_string($tablename); } private function checkExistsTable($tablename): Bool { $query = $this->dbconnect->query("SHOW TABLES LIKE '{$tablename}'"); if ($query->num_rows > 0) { return true; }else{ return false; } } public function getAll(): Array { $tablename = $this->checkExistsTable($this->tablename); if ($tablename) { $query = $this->dbconnect->query("SELECT * FROM {$this->tablename}"); $result = $query->fetch_array(); return [ 'error'=>false, 'msg_error'=>false, 'content'=>$result ]; }else{ return [ 'error'=>true, 'msg_error'=>'Указанная таблица не существует', 'content'=>false ]; } } }
теоретически можно... только непонятно - зачем наследоваться от DBConnect, смысл модели в том что она может работать с данными... а откуда и как данные берутся - неважно... сейчас у вас из БД а потом раз надо чтоб данные с json файла брались ... все переписывать? и checkExistsTable - вы что в каждой модели будете определять ? Если уж на то пошло - это все должно быть в checkExistsTable
Опять скрещиваете ежика и ёлку. Что общего у ёжика и у ёлки? Ежик живой, кушает яблоки, колит иголками, фырчит. Дерево тоже живое, оно растет, генерирует кислород, питается из земли. У обоих есть иголки. Но можем ли мы сказать что дерево ежик родственник дерева? Однозначно нет. Так же и с моделью и бд. Что делает дб? Может подключаться и работать с таблицами. А Модель всего лишь указывает БД что и откуда надо взять или обновить, так же знает типы значений и манипулирует ими. Зачем наследовать модель от базы данных? А вдруг вам придется брать значения из CSV, JSON? Создавать еще одну модель с тем же функционалом, только под каждый тип данных? В общем, на пальцах: 1. Пользователь зашел по адресу http://example.com/?controller=CarController&method=showAllCars 2. Сервер делает запрос к единой точке входа (index.php), где запускается все приложение. В том числе и роутер. 3. При запуске роутера, он вызывает контроллер и его метод (если оба существуют). Если нет, то вызывается допустим ErrorController и его метод pageNotFound PHP: <?php $controller_name = $_GET['controller']; $method_name = $_GET['method']; if( class_exists($controller_name) && $controller_name instanceof Controller::class && method_exists($controller_name, $method_name) ) { $controller = new $controller_name; $controller->$method_name(); } else { $controller = new ErrorController; $controller->pageNotFound(); } 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 и так выдаст ошибку, если таблицы нет.
Controller - Логика. В основном здесь условия, взаимодействие с моделями и вызов рендера через view. Здесь никаких запросов к бд и уж тем более html кода. Model - работа с данными, в большинстве случаев работа с бд. здесь никакой логики, а уж тем более html кода, только код для работы с базой. View - шаблон, здесь ни логики ни работы с данными нет. я допускаю в своих проектах максимум проверки типа if и т.п. Посмотрите на проекты под CodeIgniter 3, Opencart. По поводу писать свой мвс двиг или брать фреймворк - я выбрал первое, потому что написал свой мвс и съев на нем собаку (попутно изучая фреймворки, CMS-ки и т.п.) я стал больше понимать не только в MVC, но и в целом я хорошо улучшил свой скилл. --- Добавлено --- Это я бы не назвал моделью. Вместо ошибки, верните пустой массив / false, а в контроллере сделайте проверку if(empty($result)) и выводите ошибку. Модель только работает с данными, никакой логики там быть не может.
ЧЁ, правда? При работе с данными логика не нужна? --- Добавлено --- И когда говорят про логику в модели, этот термин используют в более широком смысле, чем его понимаете вы
Всмысле ?? я в моделе что хочу то и ворочу, а контроллер получается на столько минимальным, что просто устанавливает свои титлы названия страницы и запускает рендеринг. Какого только с БД работа... ? --- Добавлено --- PHP: final class MainController extends AbstractController { public function index(): CommitRepository { $this -> title( [ 'Главная', 'Не главная' ], true ); return $this -> render( 'index' ); } }
Фильтрация , валидация происходит обычно в контроллере. В модель передаются проверенные данные. Если что то не проходит проверку, до модели это не доходит. Вот я о чем. Я думаю что довольно понятно изложил суть MVC как её понимаю я. --- Добавлено --- Я предпочитаю использовать MVC так как я его описал. Код (Text): https://lectureswww.readthedocs.io/_images/mvc.svg
Мне не интересно. Работал с большими проектами в крупной компании. Сами читайте. Автор пишет, что тоже работал с фреймворками на принципах MVC, но ничего не понял. Вам хочется поумничать, ну так вперёд, объясняйте дальше и очень подробно, не ленитесь.
Зачем MVVM? Это все тот же MVC. Просто необходим ли C, чтобы V взял данные из M? Что, V напрямую из M брать не может? А что будет делать C? А C будет выполнять какую-то другую функцию, а не посредничество от M к V.
Какая-то важная часть данных окажется недоступна и все. Будете нагружать вьюшку всякой хренью для обработки ошибок? Даже исключения, возникающие при выполнении вьюшки, значительно сложнее обрабатывать.
А что будете делать вы, когда вам скажут написать тесты? С должны брать М и передавать во V. Неважно как, через репозитории (репозитАрии) или из самой модели. Если должна выполнится какая-либо бизнес логика, то она должна быть вынесена в Сервис. В контроллере ловим исключения и показываем результат. Таким образом мы можем спокойно тестировать сервисы и запускать их в терминале.
@alexphp, дайте пример, где это точно нужно, где без этого не обойтись. View по идее должен получать массив данных, которые нужно расставить по местам. Логика там может быть, но только для создания конечного результата. Например, скрыть блок такой-то от определенных клиентов, кому-то показать, или вывести таблицу особым образом. Если View чего-то не хватает, и он запрашивет дополнительные данные из Model, то что-то сделано не так, повозка начала управлять лошадью. И тесты, конечно. Без них только что-то очень маленькое делается, "на коленке". Кстати, если на выходе только JSON, View вообще не нужен.
С берёт данные через M (а M бывает по 50 штук в папке, по 40 тысяч строк кода каждая, потому нужно ещё с умом выбирать необходимые M), и в зависимости от правил роутера возвращает JSON, либо XML, либо подключает V для рендера.
Мой Viewer служит лишь как раскладчик/сортировщик данных. Ему неизвестны, не контроллер, не модели, не App с контейнером, он не знает что творится в приложении, кроме Request, Response и некоторых данных для шаблонов. Каких некоторых ? Путь к шаблонам, имя слоя стандартного ( default-template ), расширение шаблонов ( стандарт *.php ), title, head. Всё берется дейфолтом с конфига. PHP: \View :: class => static function ( ContainerInterface $container ) { $app = $container -> get( 'app' ); $repository = $app -> repository; $viewer = new \Nouvu\Web\View\Viewer( $app -> request, $app -> response ); foreach ( [ 'path', 'layout', 'extension', 'title', 'head' ] AS $name ) { $viewer -> { 'set' . ucfirst ( $name ) }( $repository ); } return $viewer; }, PHP: <?php declare ( strict_types = 1 ); namespace Nouvu\Web\View; use Symfony\Component\HttpFoundation\{ Request, Response }; use Nouvu\Web\View\Builder\Content; use Nouvu\Web\View\Repository\{ CommitRepository, HeadRepository, TitleRepository }; use Nouvu\Web\Component\Config\Repository; final class Viewer { public HeadRepository $head; public TitleRepository $title; private string $directory; private string | null $layout = null; private string $extension = '.php'; public function __construct ( private Request $request, private Response $response ) { $this -> head = new HeadRepository( [ 'list' => [], 'selected' => [] ] ); $this -> title = new TitleRepository( [ 'list' => [], 'delimiter' => ' - ' ] ); } public function setPath( Repository $repository ): void { $path = $repository -> get( 'app.system.directory.view' ) . $repository -> get( 'config.theme' ); $this -> directory = rtrim ( $path, '\\/' ) . DIRECTORY_SEPARATOR; } public function setLayout( Repository $repository ): void { $this -> layout = $repository -> get( 'config.default_template' ); } public function setExtension( Repository $repository ): void { $this -> extension = $repository -> get( 'viewer.extension' ); } public function setHead( Repository $repository ): void { $this -> head -> add( 'list', $repository -> get( 'viewer.head' ) ); } public function setTitle( Repository $repository ): void { $this -> title -> set( $repository -> get( 'config.default_title' ) ); } public function render( CommitRepository $commit ): void { $commit -> get( 'layout' ) ?? $commit -> reset ( 'layout', $this -> layout ); $commit -> reset ( 'commit', 'render' ); $commit -> replace( 'content', 'container.content' ); $commit -> replace( 'layout', 'container.layout' ); } public function redirect( CommitRepository $commit ): void { $commit -> reset ( 'commit', 'redirect' ); $commit -> replace( 'path', 'container' ); } public function json( CommitRepository $commit ): void { $this -> render( $commit ); $commit -> reset ( 'commit', 'json' ); } public function custom( CommitRepository $commit ): void { $commit -> reset ( 'commit', 'custom' ); } public function filter( CommitRepository $commit ): void { $commit -> set( array_filter ( $commit -> all() ) ); } public function filling( CommitRepository $commit ): void { foreach ( [ 'directory', 'layout', 'head', 'title', 'extension' ] AS $name ) { $commit -> reset ( $name, $this -> {$name} ); } } public function terminal( CommitRepository $commit ): void { $this -> filter( $commit ); $this -> filling( $commit ); $this -> send( $commit, new Terminal( $commit ) ); } private function send( CommitRepository $commit, Terminal $terminal ): void { match ( $commit -> getCommit() ) { 'render' => $terminal -> contentResponse( $this -> response, new Content( $commit ) ), 'redirect' => $terminal -> redirectResponse( $this -> response ), 'json' => $terminal -> jsonResponse( $this -> response, new Content( $commit ) ), 'custom' => $terminal -> customResponse( $this -> response ), }; $this -> response -> prepare( $this -> request ); $this -> response -> send(); } } После сортировки, у него дергают терминал. От требуемого типа действия, собранная/отсортированная информация в CommitRepository, попадает в этот терминал для составления документа/ответа. PHP: <?php declare ( strict_types = 1 ); namespace Nouvu\Web\View; use Symfony\Component\HttpFoundation\Response; use Nouvu\Web\View\Builder\Content; use Nouvu\Web\View\Repository\CommitRepository; class Terminal { public function __construct ( private CommitRepository $commit ) { // ... } public function contentResponse( Response $response, Content $build ): void { $response -> headers -> set( 'Content-Type', 'text/html' ); $build -> setContent( $response ); } public function redirectResponse( Response $response ): void { $response -> setStatusCode( Response :: HTTP_MOVED_PERMANENTLY ); $response -> headers -> set( 'Location', $this -> commit -> getContainer() ); } public function jsonResponse( Response $response, Content $build ): void { $response -> headers -> set( 'Content-Type', 'application/json' ); $build -> setContent( $response, function ( string $content ) use ( $build ): string { return json_encode ( [ 'response' => 'content', 'content' => $content, 'title' => $build -> getTitle(), ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); } ); } public function customResponse( Response $response ): void { $closure = $this -> commit -> get( 'closure' ); $closure( $response ); } } Кстати, в CommitRepository находится объект контроллера и так же запущенные модели. Терминал... В зависимости от типа запроса, тело документа так же отдельно обрабатывается в билдере Content и возращается обратно в терминал. Builder\Content вовсе не знает ничего о приложении, нету доступа к окружению, только CommitRepository через которого может взаимодействовать только с контроллером и моделями. PHP: <?php declare ( strict_types = 1 ); namespace Nouvu\Web\View\Builder; use Symfony\Component\HttpFoundation\Response; use Nouvu\Web\View\Repository\CommitRepository; use Nouvu\Web\View\Builder\ShortTag; use Stringable; class Content { private string $content = ''; public function __construct ( private CommitRepository $commit ) { // $this -> commit -> reset( 'content', '' ); } protected function replaceCode( string $template, string $content ): Stringable { return new ShortTag( [ $this, $this -> commit -> get( 'controller' ), ...$this -> commit -> get( 'models', [] ) ], function ( array $matches ) use ( $template ): string { $file = dirname ( $template ) . DIRECTORY_SEPARATOR . $matches[1]; if ( file_exists ( $file . $this -> commit -> get( 'extension' ) ) ) { return $this -> getHtml( $file ); } return "<!-- Not found ({$matches[1]}) -->"; }, $content ); } public function setContent( Response $response, callable | null $call = null ): void { foreach ( array_filter ( $this -> commit -> getContainer() ) AS $name ) { $this -> content = $this -> getHtml( $this -> commit -> get( 'directory' ) . $name ); } if ( is_callable ( $call ) ) { $response -> setContent( $call( $this -> getContent() ) ); return; } $response -> setContent( $this -> getContent() ); } public function getContent(): string { return $this -> content; } public function getTitle(): string { return $this -> commit -> get( 'title' ) -> getResult(); } public function getHead( string ...$heads ): string { if ( func_num_args () > 0 ) { $this -> commit -> get( 'head' ) -> add( 'selected', func_get_args (), true ); } return implode ( PHP_EOL . "\t", iterator_to_array ( $this -> commit -> get( 'head' ) -> getResult() ) ); } public function getHtml( string $name ): string { ob_start (); $closure = $this -> commit -> get( 'controller' )(); $closure( $name ); return ( string ) $this -> replaceCode( $name, ob_get_clean () ); } } Его работа заключается в наложении на слой шаблона требуемый контент, обработка "моих шорт кодов, которые дергают публичные методы с приставкой get, пример: {<{username}>} ищем метод getUsername у себя в Content, в контроллере, после в моделях". Но перед всем этим - дергается сначала контроллер и в нем, если логика требуется, запускаем нужные модели хоть 100500 раз.
Сами модели с контроллером имеют доступ к приложению. PHP: <?php declare ( strict_types = 1 ); namespace Nouvu\Web\Http\Controllers; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Nouvu\Web\Foundation\{ Application, ApplicationTrait }; use Nouvu\Web\View\Repository\CommitRepository; class AbstractController { use ApplicationTrait; public function __construct ( protected Application $app ) { } .... protected function render( string $content = '', string | null $layout = null ): CommitRepository { if ( $this -> isAjax() ) { return $this -> json( $content ); } $commit = $this -> getCommitInstance( compact ( 'content', 'layout' ) ); $this -> app -> view -> render( $commit ); return $commit; } public function __invoke() { return $this -> app -> repository -> get( 'viewer.include' ); } } PHP: <?php declare ( strict_types = 1 ); namespace Nouvu\Resources\Models; use Nouvu\Web\Foundation\Application AS App; use Nouvu\Web\Component\Validator\Exception\ViolationsException; class AbstractModel //implements ???... { public function __construct ( protected App $app ) { } } Ну а по запросу приложения, запускается определенный контроллер и выполняет свои действия... PHP: <?php declare ( strict_types = 1 ); namespace Nouvu\Resources\Controllers; use Nouvu\Web\Http\Controllers\AbstractController; use Nouvu\Web\View\Repository\CommitRepository; final class MainController extends AbstractController { public function index(): CommitRepository { $this -> title( [ 'Главная', 'Не главная' ], true ); return $this -> render( 'index' ); } public function welcome( $slug = null ): CommitRepository { $this -> title( [ 'Welcome' ] ); return $this -> render( 'welcome' ); //return $this -> render( 'test/login/login', 'test/login/template' ); } public function err404(): CommitRepository { $this -> title( [ 'Страница не найдена' ], true ); return $this -> render( 'error.404', 'error-template' ); } public function err500(): CommitRepository { $this -> title( [ 'Ошибка сервера' ], true ); return $this -> render( 'error.500', 'error-template' ); } public function testError(): CommitRepository { require '1.php'; // Test error // $this -> getModel( 'auth.test' ); return $this -> render( 'index' ); } } --- Добавлено --- HTML: <!DOCTYPE html> <html lang="{<{locale}>}"> <head> <title>{<{title}>}</title> {<{head=meta-charset|meta-viewport}>} </head> <body> {<{content}>} </body> </html>