Основы ООП и контроллеры страниц
На этом занятии мы начнём писать ядро нашего сайта и заложим основу для работы контроллеров страниц.
Ядро - это один основной класс, с общими для всех контроллеров методами. А контроллеры - это другие php классы, которые будут отвечать за функционал какого-то раздела сайта.
Контроллеры будут лежать в специальной директории, откуда их запустит основной класс. Запрос приходит на index.php, тот инициализирует основной класс и просит обработать запрос. Ядро определяет, к какому разделу сайта обращён запрос и загружает его контроллер, передавая ему все полномочия на генерацию ответа.
Дальше контроллер проверит параметры запроса и выдаст результат, или редирект на страницу с ошибкой. Позже мы научим наши контроллеры обрабатывать запросы и выдавать ответы через Ajax.
Логическая цепочка выходит такая: index.php -> Сore -> Controllers_Page.
Что такое ООП
Объектно-ориентированное программирование - это когда вы оперируете не набором файликов php, а набором объектов. Конечно, они располагаются в файлах, но представляют собой не разрозненный набор функций, а готовый кусок какого-то функционала.
В литературе обычно приводят примеры рельных объектов, типа дома или автомобиля, у которых есть свойства (окна, руль, колёса) и методы (включить свет, поехать). Лично мне эти примеры всегда были не очень понятны, поэтому я попробую объяснить принципы работы классов иначе.
Итак, класс - это готовая к употреблению программа, которая выполняет какие-то функции. Вы можете подключить к себе на сайт эту программу, чтобы она выполняла эти функции для вас.
Структурно PHP класс представляет собой всё тот же набор свойств и методов. Свойства - это просто переменные внутри класса и снаружи его методов. А методы - это обычные функции внутри класса.
По сути любой php класс выглядит так:
<?php
class Core {
public $test = 1;
public function test() {
return 'Hello World!';
}
}
Если здесь убрать объявление класса и слова public - то получится обычный php код, не правда ли? Так зачем вообще нужно заморачиваться с этими классами? А затем, что у них есть замечательные особенности:
- Внутри класса всё выполняется изолированно. Переменные и функции внутри класса и снаружи никак не пересекаются и не выдают ошибок о том, что такая функция уже объявлена.
- Вы, как автор класса, можете указать, какие методы можно выполнять снаружи, а какие исключительно для внутреннего использования.
- Класс может быть расширен другим классом, и его методы могут быть переопределены. Конечно, если вы, как автор класса, это разрешили.
- Все методы внутри класса могу обращаться друг к другу без ограничений. Вы моежет хранить внутри класса данные так, чтобы их не было видно снаружи и их нельзя было изменить.
В конце концов класс - это удобная упаковка для готового функционала, которую можно перетаскивать из одного проекта в другой с минимальными изменениями.
Давайте же начнём писать наш первый класс с ядром сайта!
Core.php
Так как мы только начинаем изучать ООП в PHP, мы пока не будем следовать стандартам PSR: использовать пространства имён, автозагрузчик и прочее. Мы просто пишем рабочий код и, возможно, на последнем занятии отрефакторим его правильно.
Итак, создаём директорию /core/, в ней файл Core.php:
<?php
class Core {
public $config = array();
/**
* Конструктор класса
*
* @param array $config
*/
function __construct(array $config = array()) {
$this->config = array_merge(
array(), $config
);
}
}
Что здесь происходит?
Во первых - объявление класса:
class Core {}
Внутри класса объявляем публичную переменную config (про области видимости чуть ниже). Она будет нужна нам для выставления разных настроек в работе класса.
Дальше следует специальный метод __construct() - он выполняется всегда один раз, при создании нового экземпляра класса.
Экземпляр класса - это его, как бы, копия, которую мы получаем при инициализации класса и можем использовать как угодно. Можно создавать неограниченное количество экземпляров класса, что есть еще одна гибкость ООП.
При инициализации наш класс будет соединять массив стандартных настроек (пока что пустой) и переданный массив настроек от пользователя. А дальше, внутри класса, можно будет к этим настройкам обращаться через $this->config.
Вообще, все обращения к методам экземпляра класса происходят через $this. То есть, экземпляр обращается, таким образом, сам к себе.
Инициализация класса
Мы уже определились, что вся работа у нас идёт через index.php. Директорию core вообще, по-хорошему, нужно закрыть от всех запросов извне.
Так что, в index.php нам и нужно инициализировать Core:
if (!class_exists('Core')) {
require_once 'core/Core.php';
}
$Core = new Core(array('test' => 'Yes!'));
print_r($Core->config);
Здесь мы, на всякий случай, проверяем, вдруг класс Core уже был загружен, и если нет - подключаем файл с ним. Затем создаём новый экземпляр класс командой new и передаём наш массив параметров конструктору.
После этого мы можем распечатать конфиг этого экземпляра, он нам выведет
Array ( [test] => Yes! )
И вот теперь смотрите, как можно делать с классами:
$Core1 = new Core(array('test' => 'Yes!'));
$Core2 = new Core(array('test' => 'No!'));
print_r($Core1->config);
print_r($Core2->config);
На выходе:
Array ( [test] => Yes! ) Array ( [test] => No! )
Два независимых экземпляра, каждый со своими настройками. Они будут работать немного по-разному в зависимости от своего config, при этом, методы внутри них одни и те же.
Теперь про области видимости. Переменные и методы внутри класса могут быть трёх разных видов:
- public - любой скрипт снаружи может прочитать и записать это свойсто, или выполнить метод.
- protected - доступ есть только у класса, который наследует и расширяет наш класс.
- private - доступ есть только у самого класса
Таким образом, когда мы объявили переменную $config как public - мы разрешили доступ к ней снаружи. Поэтому index.php может распечатать массив с настройками.
Давайте теперь объявим новый публичный метод handleRequest, куда перенесём логику по обработке запроса из index.php.
Метод обработки запроса
Пишем в Core.php:
/**
* Обработка входящего запроса
*
* @param $uri
*/
public function handleRequest($uri) {
// Массив доступных страниц
$pages = array('home', 'test');
// Определяем страницу для вывода
$page = '';
// Если запрос не пуст - проверяем, есть ли он в массиве наших страниц
$request = explode('/', $uri);
// Если есть - окей, всё верно, используем это имя
if (in_array(strtolower($request[0]), $pages)) {
$page = strtolower($request[0]);
}
// Иначе используем страницу по умолчанию
if (empty($page)) {
$page = 'home';
}
echo "Мы выводим страницу <b>{$page}<b>";
}
Как видите, мы указали в методе обязательный параметр uri, который должен передать вызывающий скрипт. В нашем случае, это index.php, который мы меняем вот так:
if (!class_exists('Core')) {
require_once 'core/Core.php';
}
$Core = new Core();
$req = !empty($_REQUEST['q'])
? trim($_REQUEST['q'])
: '';
$Core->handleRequest($req);
Он проверяет, есть ли в запросе от сервера наша переменная q, и если нет - то запрошена корневая страница. Дальше в handleRequest передаётся или запрос или пустота, пусть уже он сам разбирается. Проверка на существование $_REQUEST['q'] нужна для того, чтобы не было обращения к несуществующему элементу массива и E_NOTICE вслед за этим (которую мы увидим, потому что заранее включили вывод всех сообщений в index.php на прошлом уроке).
Выгружаем всё на сервер и проверяем. Должно быть тоже самое, что и раньше:
Мы выводим страницу home
по адресу http://s1889.bez.modhost.pro и
Мы выводим страницу test
по адресу http://s1889.bez.modhost.pro/test/
Зачем вообще эта чехарда с новым классом и специальным методом в нём для обработки запроса, ведь мы могли оставить всё это и в index.php?
Нет, не могли, потому что мы не хотим забивать намертво все возможные разделы сайта.
Мы хотим сделать отдельную директорию с контроллерами этих разделов, и доработать наш handleRequest таким образом, чтобы он сам проверял - есть ли обработчик для запроса, или нет? Если есть, запускал бы его, а если нет - выводил ошибку 404.
На данном этапе исходники нашего сайта выглядят вот так.
Контроллеры разделов
Давайте создадим новую директорию /core/controllers/ и добавим в неё класс Home.php:
<?php
class Controllers_Home {
/** @var Core $core */
public $core;
/**
* Конструктор класса, требует передачи Core
*
* @param Core $core
*/
function __construct(Core $core) {
$this->core = $core;
}
/**
* Основной рабочий метод
*
* @return string
*/
public function run() {
return "Мы выводим страницу <b>Home<b>";
}
}
А рядом создадим файл такой же файл Test.php, с чуть изменённым методом run():
public function run() {
return "Мы выводим страницу <b>Test<b>";
}
Как видите, эти 2 контроллера требуют, чтобы им при инициализации был передан экземпляр класса Core. Это прописано у них в методе __construct(Core $core);.
Теперь нам нужно доработать наш метод handleRequest, чтобы он сам определял, какой раздел сайта у нас запросили и передавал работу нужному контроллеру.
/**
* Обработка входящего запроса
*
* @param $uri
*/
public function handleRequest($uri) {
// Определяем страницу для вывода
$request = explode('/', $uri);
// Имена контроллеров у нас с большой буквы
$name = ucfirst($request[0]);
// Полный путь до запрошенного контроллера
$file = $this->config['controllersPath'] . $name . '.php';
// Если нужного контроллера нет, то используем контроллер Home
if (!file_exists($file)) {
$file = $this->config['controllersPath'] . 'Home.php';
// Определяем имя класса, согласно принятым у нас правилам
$class = 'Controllers_Home';
}
else {
$class = 'Controllers_' . $name;
}
// Если контроллер еще не был загружен - загружаем его
if (!class_exists($class)) {
require_once $file;
}
// И запускаем
/** @var Controllers_Home|Controllers_Test $controller */
$controller = new $class($this); // Передавая экземпляр текущего класс в него - $this
$response = $controller->run();
echo $response;
}
Внимательно читаем код и комментарии, проникаясь идеей: контроллер загружается автоматически, в зависимости от запроса, и вся дальнейшая работа передаётся ему.
Вот, у нас уже есть разделы сайта! Каждый контроллер имеет доступ к основному классу через свою переменную $this->core, которая выставляется при его инициализации. Если попробовать запустить контроллер без Core - будет ошибка.
Снаружи на сайте всё выглядит как и раньше, выводятся 2 надписи. Но вы уже знаете, что за каждую надпись отвечает свой контроллер. Если мы захотим, то можем добавить новую страницу /news/, для работы которой нужно будет создать соответствующий контроллер /controllers/News.php - и никаких изменений в Core и его handleRequest не понадобится!
Если же юзер запросит несуществующую страницу (то есть такую, для которой нет контроллера), то он получит страницу Home. Пока без кода 404, его мы добавим позже.
Вы должны были заметить, что в handleRequest я обращаюсь в $this->config за указанием пути к контроллерам. Да, всё верно, я немного изменил его __construct() вот так:
function __construct(array $config = array()) {
$this->config = array_merge(
array(
'controllersPath' => dirname(__FILE__) . '/controllers/',
),
$config
);
}
То есть, указал директорию по умолчанию, в которой лежат контроллеры. Она берётся от директории текущего файла (а это Core.php, потому что всё дело происходит в нём) и index.php теперь может переопределить путь к контроллерам сайта, если захочет. В Core у нас от этого ничего не сломается.
Еще, возможно вас гложет вопрос, что это странные такие комментарии перед объявлением методов и инициализацией переменных, типа:
/** @var Controllers_Home|Controllers_Test $controller */
Очень просто - это комментарии в формате PHPDoc, чтобы будущим пользователям вашего кода и вашей IDE было понятно, что вы имеете в виду в тех или иных местах.
PhpStorm умеет генерировать эти комментарии самостоятельно, а вам я советую прочитать о них на Википедии или на официальном ресурсе.
Заключение
Вот мы и написали с вами сразу аж 3 класса и научились их использовать для обработки запросов страниц.
На данный момент у нас есть index.php, основной класс Core с настройками и 2 контроллера для обработки страниц: Controllers_Test и Controllers_Home. Если запрошенной страницы на сайте нет, то выводится Home.
На следующем уроке мы напишем базовый контроллер, куда сложим все основные методы обработки страниц, а Test и Home будут его расширять. Это сэкономит нам кучу времени и байтов, потому что нам не придётся дублировать один и тот же код в двух разных файлах.
Надеюсь, вам всё понятно. Если же нет - жду комментариев с вопросами. На данном этапе наш исходный код выглядит вот так.
0
👍
👎
❤️
🔥
😮
😢
😀
😡
6 376
25.05.2015, 13:46:00
24 комментария
27.06.2015, 23:02:22
Понятно, что это метод (функция), понятно, что такое конструкт, но вот, что в скобках... просто не понимаю зачем и что делает.
Или вот еще момент
Я так понимаю, таким способом мы передаем информацию, но как получается, что она попадает именно в
И таких вопросов много, если их все задавать....
27.06.2015, 23:38:25
В скобках функции указываются параметр, которые она принимает.
array $config = array() - это приём параметра $config, который может быть только массивом и по умолчанию - пустой массив.
Если передать в функцию строку или число (что угодно, кроме массива) - будет ошибка. А если не передавать ничего, то $config внутри метода будет пустым массивом.
Собственно, при вызове
Мы и передаём массив параметров, который внутри __construct становится переменной $config - как и написано в объявлении фунцкии.
27.06.2015, 23:47:19
Получается так:
array — это условие, что может принимать метод, а $config = array() - это значение по умолчанию?
Такая конструкция передаст параметры всегда в __construct?
28.06.2015, 01:20:30
$config - это переменная, которую принимает конструктор класса = array() - это её значение по умолчанию, если мы ничего не передаём а начальный array - это требование того, что передаваемые данные могут быть только массивом.
Можно указать и так:
И тогда функция примет любую переменную, не только массив
А можно и так:
и тогда функция будет требовать передать ей что-то на вход, иначе ошибка (потому что нет значения по умолчанию) и класс не запустится.
28.06.2015, 01:37:09
Спасибо, сейчас все понятно!
Адиль
30.01.2016, 11:51:20
Василий массив $config указанный в методе __construct и публичный член класса $config который объявлен вами ранее это одно и тоже или что?
Адиль
30.01.2016, 11:55:43
затем далее ты в теле метода __construct пишешь
что за массив array() который ты сливаешь с массивом $config откуда ты его берешь и откуда он поступает в метод construct?
30.01.2016, 12:00:34
Адиль, давай я лучше тебе сразу верну деньги?
Судя по уровню вопросов, ты мне мозг вынесешь так, что я прокляну эти 2000 руб.
Адиль
30.01.2016, 12:07:07
Василий нет не надо возвращать, я хочу научиться прояви пожалуйста терпение, я тебя очень прошу. Просто хочу понять отвечай как тебе позволяет время, если ты занят то я могу и потерпеть. а так на чет уровня вопросов зря ты так я тут прочел ленту комментариев и ты тут на олее примитивные вопросы отвечаешь, просто может я не так вопрос сформулировал.
30.01.2016, 12:11:38
Вот что здесь непонятно и заставило тебя задать вопрос?
Прямо под этим кодом написано > Что здесь происходит? ... Внутри класса объявляем публичную переменную config (про области видимости чуть ниже). Она будет нужна нам для выставления разных настроек в работе класса.
Я делаю вывод что ты не читаешь, или не хочешь понимать мой текст. Соотвественно, мне проще вернуть тебе сразу деньги, чтобы не иметь таких бесед как сейчас. Я просто больше времени и нервов потрачу.
Адиль
30.01.2016, 12:14:18
Василий я прочел и читаю по несколько раз то что ты пишешь, поверь мне на слово и то что написано тобою я тоже прочел. Просто в коде твоем я ни где еще не встретил то что ты передаешь пользовательский массив array в конструктор класса. Как я понял чтоб мне тебя не злить мне надо сначала прочесть весь курс и при повторном прочтении задавать вопросы, так можно?
Адиль
30.01.2016, 12:15:49
Василий не злись пожалуйста, я же набираю знания может ты более умнее чем я и все схватываешь на лету, просто у меня такая шиза всегда присутствовала я люблю глубоко погружаться и если встречаю в коде что то мне не понятное освоение материала тормозится
30.01.2016, 12:18:41
Причем здесь "злить"? Я уже пообщался с тобой на modx.pro, я уже пообщался с тобой на modstore.pro и сейчас общаюсь на bezumkin.ru.
Из этого общения я делаю вывод, что ты не знаешь PHP, вообще, и очень хочешь научиться. Но у меня нет ни времени, ни желания учить тебя с нуля. Судя по твоему объявлению - никто не хочет этого делать.
В общем, читай курс, если и дальше будут вопросы, на которые уже даны ответы в тексте - я возвращаю тебе деньги и дело с концом.
Адиль
30.01.2016, 12:21:10
Хорошо Василий.
06.08.2015, 03:05:07
Василий, с помощью http://php.net вроде разобрался. правильно понял что при запуске Core.php сначала поместили массив из файлов директории /controllers/ в переменую config, а в функции handleRequest сначала преобразовываем запрос пользователя под тип значений массива, и проверяем есть ли такой -> выдаем нужный контроллер директории сайта. так долго разбирался с синтаксисом наверно мне нужен какой то задачник (как в школе по математике) для изучения этого множества функций как раз твой курс будет первым
06.08.2015, 08:24:30
Не совсем так. При инициализации класса в конфиг мы передаём только строку с путём до директории контроллеров.
А дальше метод handleRequest при обработке запроса смотрит, какие в этой директории есть файлы, выбирает подходящий контроллер и передаёт работу ему. Если подходящего контроллера нет - то выдаёт ошибку.
Передавать готовый список контроллеров не очень удобно. Гораздо приятнее просто добавлять или удалять файлы контроллеров в одной директории, без изменения кода handleRequest.
06.08.2015, 11:57:14
спасибо! ещё понятнее, ночью как то по другому работает голова :)
25.12.2015, 16:00:51
Чуть подзапоздал с изучением курса, но все таки добрался. Надеюсь вопросы еще уместны и будет время, чтобы на них ответить.
Насколько я понял, этой строкой мы подключаем Ядро, и далее будем его расширять. Слово Core это какой то встроенный в ядро PHP оператор? Как он работает? Выше был пример с array - там было понятно, что принимаются только массивы, а строки и числа заворачиваются и идут лесом.
Далее что конкретно должно приходить в $core? Какие данные?
25.12.2015, 16:06:14
Ответ на второй вопрос нашел но не до конца понял
При вызове класса Controller_Home в него передается экземпляр Ядра. Он передается в виде объекта? Так что становятся доступны все его методы и свойства. Верно я понимаю?
25.12.2015, 16:35:45
Верно, да.
И в конструкторе контроллера требуется именно экземпляр класса Core, если передать что-то другое, то будет ошибка и контроллер не запустится.
То есть, мы еще на стадии создания контроллера требуем передачи в него только определённого класса, и класс этот - Core, то бишь наше ядро.
25.12.2015, 16:42:40
А не проще написать что то типа: ?
Это даст тот же эффект?
25.12.2015, 16:45:54
Это расширение одного класса другим, используется обычно для изменения работы методов. В принципе, можно и так, но это менее гибко.
Мы же передаём один класс в другой и отделяем таким образом контроллер от ядра. Дальше будет понятнее, в чём удобство.
Адиль
30.01.2016, 16:41:02
Василий при $class = 'Controllers_Home'; понятно что в $controller = new $class($this); будет передан объект класса Core и при $class='Controllers_Test' понятно что в $controller = new $class($this); будет храниться объекта класса Controllers_Test ну вот что передается в качестве параметра не совсем понятно, что значит ($this)? при значении $class='Controllers_Test' это значение публичного члена $core? или что, не совсем понял.
30.01.2016, 16:44:47
$this - это всегда экземпляр текущего класса, внутри которого происходит действие.
В данном случае да, это экземпляр Core, потому что действие происходит внутри него.
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так.
А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен.
Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500
Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи.
...
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо!
Извини, тупанул.