Приветствую всех! В данной статье я расскажу про процесс создания собственного школьного электронного журнала.
С чего все началось? Началось всес того, что я купил книгу по PHP/MySQL, поскольку давно увлекался проектами на школьную тематику, те же электронные журналы. После пары прочтенных страниц я сел за проект. Проект получил название АИС «Школьник» (Автоматизированная Информационная Система «Школьник»).
Какова была главная цель проекта? Главной целью проекта стала реализация системы, чтобы ей могли пользоваться бесконечное количество школ и были органы управления, которые могли создавать школы и просматривать их статистику (об этом расскажу ниже).
Первым делом я накидал простенькую шапочку на главную страницу:

И простенькую форму для входа:

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

В разделе «Моя школа» отображались сведения о школе — краткое наименование, полное наименование, почта директора, тип организации (школа/гимназия/лицей), ФИО директора, дата основания организации.
В разделе «Настройка школы» можно было добавить учеников, сотрудников, а также настроить предметы, которые преподаются в школе и классы.
В разделе «Настройка расписания» было все для составления расписания: схемы звонков, схемы периодов (границы четвертей), схемы расписания, нагрузка по преподавателям.
Как были реализованы периоды и звонки на тот момент?
На тот момент можно было создать только одну схему периодов или звонков на школу. Вот как это работало:
<?php
// проверяем на наличие созданной схемы периодов
$res = $conn->query("SHOW TABLES LIKE `{название_школы}_periods`");
if($res->num_rows > 0){
echo "Периоды уже созданы!";
} else{
echo "<a href='#'>Создать схему</a>";
}
?>
С звонками точно также, только вместо {название_школы}_periods - {название_школы}_calls.
Таблица periods:
CREATE TABLE `school_periods`(
"period1_to" DATE NOT NULL,
"period1_from" DATE NOT NULL,
"period2_to" DATE NOT NULL,
"period2_from" DATE NOT NULL,
"period3_to" DATE NOT NULL,
"period3_from" DATE NOT NULL,
"period4_to" DATE NOT NULL,
"period4_from" DATE NOT NULL
)
Да, настолько это было неудобно с серверной части. Я потом пожалел, что сделал так (об этом ниже).
Таблица calls:
CREATE TABLE `school_calls`(
"dayid" INTEGER NOT NULL,
"from" TIME NOT NULL,
"to" TIME NOT NULL
)
Со звонками было все намного проще, однако не было разделений на разные дни (т. е. в понедельник уроки могли начинаться с 8:00, а во вторник с 8:30).
Настройка школы
Выше было сказано, что в разделе «Настройка школы» были пункты: ученики, сотрудники, предметы. В пунктах «Ученики» и «Сотрудники» можно было добавлять персон:

Да, тогда директор самостоятельно задавал логин и пароль каждому учителю. У учеников была такая‑же форма, но при этом добавились пункты «Дата рождения» и «Класс».
Настройка расписания
Для начала нужно составить расписание звонков. Пользователю необходимо было ввести максимальное количество уроков в день. После этого на экране отображалась форма (код ниже), которую можно было заполнить 1 раз без возможности редактирования (тогда я еще не знал про AJAX и старался не использовать JS, не знаю почему, возможно, из‑за того, что я только начал писать проект и не знал о всех тонкостях. А жаль :-) Если что‑то было введено неверно, то можно либо заполнить форму заново, либо сохранить текущую схему и пересоздать её.
<?php
$lessonsAtDay = 5; // количество уроков в день. значение передавалось из формы.
echo "<form>";
foreach($lessonsAtDay as $i){
echo "Урок $i: <input type='time' name='$i-from'>-<input type='time' name='$i-to'>";
}
echo "<br><input type='submit' value='Применить'></form>";
?>

В итоге сама схема звонков выглядела так. Да, дизайн не очень, но вскоре таблицу уменьшили в размерах и поменяли цвет. Формат отображения времени на фото: ЧЧ:ММ:СС, вскоре было изменено на ЧЧ:ММ.
С периодами было все намного хуже, сейчас расскажу почему. Во‑первых, структура таблицы периода — вы ее могли наблюдать выше. Из‑за этого было очень трудно сделать гибкую систему создание периодов (чтобы пользователь мог делать не только четверти, но и семестры или полугодия). Во‑вторых, периоды нельзя было пересоздать! То есть, если вы допустили ошибку при указании даты, вы не сможете её поменять.
Нагрузка была самой легкой из данного раздела.

Здесь было все просто. Я сделал форму с выбором класса, а потом предмета и его преподавателя, который проводится в классе:
// Пример вывода списков предметов и преподавателей
<?php
require "config.php";
if(isset($_POST["group"])){
// вывод предметов
$res = $conn->query("SELECT `lesson` FROM `lessons` WHERE `school` = '$currentschool'"); // currentschool находится в конфиг-файле
echo "<select>";
foreach($res->fetch_assoc() as $lesson){
echo "<option>$lesson</option>";
}
echo "</select>"
$res = $conn->query("SELECT `fullname` FROM `users` WHERE (`role` = 'Учитель' OR `role` = 'Завуч' OR `role` = 'Директор') AND `school` = '$currentschool'"); // currentschool находится в конфиг-файле
echo "<select>";
foreach($res->fetch_assoc() as $teacher){
echo "<option>$teacher</option>";
}
echo "</select>"
}
?>
Таблица, где хранилась вся назначенная нагрузка называлась workload
:
CREATE TABLE `workload`(
"lesson" TEXT NOT NULL,
"teacher" TEXT NOT NULL,
"group" TEXT NOT NULL,
"school" TEXT NOT NULL
)
Во всех таблицах, которые не принадлежат определенной школе (с названием {school}_*
) всегда есть столбец school
. Это сделано в целях того, чтобы у каждой школы отображалась только своя информация (или же со значением названия данной школы)
В данном случае нагрузка была удобной и гибкой, ведь если нужно внести какие-либо изменения в структуру таблицы, то не нужно было лезть во все таблицы, созданные школами.
Время перейти к самому составлению расписания. Его я оттягивал очень долго, ведь этот пункт был самым сложным для меня, но сроки были все меньше и надо было сделать много чего, поэтому я принял решение сделать расписание, но это было не в привычном нам понимании: создал схему, расставил уроки и опубликовал. Каждый преподаватель должен был самостоятельно создавать уроки. Понимаю, что это неудобно, но вот, как это выглядело:


Учтите, что можно было выбрать не любой предмет, а только тот, который ведет преподаватель.
SELECT `lesson` FROM `workload` WHERE `teacher` = 'текущее ФИО' AND `school` = 'текущая школа'

Раньше преподавателю самостоятельно нужно было выбрать время урока. Тогда можно было указать любое значение в первом пункте, тогда ничего не зависело от расписания звонков, хотя возможность создания была. Однако, в будущем обновлении я полностью убрал функцию самостоятельного создания уроков и добавил схемы, которые можно было создавать в бесконечном количестве.
Новая система публикации расписания
Спустя несколько месяцев я выпустил обновление, связанное со схемами расписания.

Конечно же про AJAX тогда и речи не шло, хотя попытки реализации были. Но из-за маленьких знаний, а точнее, нулевых знаний я не смог реализовать это. В итоге пришлось сделать такие кнопки.
В разделе "Добавить урок" была форма: номер, предмет, день недели.
В разделе "Удалить урок" была такая же форма.
А вот уже в разделе "Публикация" была ссылка которая вела на публикацию схемы. Опубликовать можно было только на одну неделю, и период приходилось выбирать самостоятельно. Автоматического тогда ничего не было.
Все схемы расписания хранились в таблице {название_схемы}_scheme
Журнал
Пример журнала я брал из старой версии Дневника.Ру:

Тогда все‑таки пришлось использовать AJAX, чтобы при каждом изменении не нажимать на кнопку «Сохранить». Это было моим единственным правильным решением на тот момент. Над дизайном тогда нужно было еще работать и работать. Уточню, что редактировать оценки можно было только на странице урока, несмотря на наличие главной страницы:

Выделю минус, что на данной странице не отображались уроки, по которым оценки не выставлены (в том числе и пропуски). 21 сентября я начал готовить журнал к публикации в народ, 1 октября система была уже для всех доступна.
Органы управления
Органы управления могли зарегистрировать школы, которые желали поучаствовать в бета‑тестировании с 15 сентября 2024 года. За 2 дня после открытия доступа было зарегистрировано более 10 школ, что для меня тогда считалось значимым результатом.
У органов управления были функции просмотра статистики школ, заморозки школ (блокировки доступа), удаление и создание школ, изменения пароля от аккаунта директора.
Также, я планировал сделать просмотр журналов школ, но не смог (об этом расскажу ниже).
Главная страница
Я считаю, что на каждом сайте должна быть своя главная страница, которая рассказывала о всех преимуществах. Я решил тоже сделать главную страницу. И была она больше рассчитана под какой‑нибудь детский сайт...

Да и сам сайт выглядел по-детски. Всему вина шрифта Montserrat, который я в будущем сменил на Lato.
Развитие проекта
Еще во время разработки проекта у меня появилась своя аудитория, которая следила за проектом. После выпуска журнала посыпались, как плохие отзывы, так и хорошие, но больше было хороших. Также, обнаружилось большое количество багов, которые я исправлял еще несколько недель. Тогда обновления выходили чуть ли не по 2 раза в день.
В самом первом обновлении я добавил публичный реестр органов, возможность комментирования органов, отправки жалобы на комментарии, а также возможность выхода из аккаунта (да, раньше такого не было и приходилось ждать сутки, чтобы закончилась сессия).
Спустя несколько обновлений, я добавил возможность редактирования оценок со страницы самого журнала:

Все шло хорошо, я добавил вход через Яндекс, Телеграмм, ВК. Но с каждым разом разработка обновлений становилась все хуже и хуже, а причиной этому была структура таблиц в БД, о которых я рассказывал выше. Поэтому, я принял решение полностью переписать проект, но уже не на PHP, который я практически не знал, а на Python с использованием Django, поскольку Python был самым родным языком. Однако, я не знал Django и я в этот же вечер, когда закрыл проект, сел изучать Django.
Проблемы в коде
В ходе статьи я уже рассказывал об одной главной проблеме проекта - структура таблиц, но и в коде есть большие проблемы! Одна из них - использование куки для хранения логина:
<?php
// так делать НЕ СТОИТ!!!
$login = $_COOKIE["login"];
?>
В проекте очень много проблем с безопасностью, еще один пример, связанный с запросами к БД:
<?php
// так делать НЕ СТОИТ!!!
$publicid = $_GET["id"];
$res = $conn->query("SELECT * FROM `communities` WHERE `publicid` = '$publicid'");
?>
Итог
С августа разработки системы у журнала появилась своя аудитория, которая активно следит за обновлениями и предлагает свои идеи. Сейчас журнал находится на стадии переписывания на другой язык программирования. Кому интересно, тот может ознакомиться с проектом на PHP на Github (не стоит использовать код отсюда для создания своего проекта, если вы не хотите потратить большую часть на исправление костылей).
Комментарии (10)
SerafimArts
04.02.2025 06:29Ну что сделал -- молодец. Однако с точки зрения практичности такой статьи - довольно сомнительно. Есть вполне обоснованное сомнение в том, что никакой практической пользы она не несёт: Код процедурный и ниже среднего качества; Архитектура... Ну такое. Есть даже места, которые ни в коем случае на реальных проектах писать не стоит.
Ну вот и получаем, что в целом из позитивного тут только: Молодец. Сделал полезный проект для себя и потренировался, научился чему. Школам таким пользоваться, очевидно, не стоит. Не только с точки зрения кодовой базы, но сколько с точки зрения огромнейшей дыры, когда public смотрит в корень проекта.
Так что желаю не останавливаться на достигнутом, но на текущем этапе проект очень далеко до поставленной цели.
timursper Автор
04.02.2025 06:29Цель статьи была в том, чтобы никто моих ошибок не повторял (код, структуру таблиц и пр.).
FanatPHP
04.02.2025 06:29Так никто и так не повторяет :) В таком стиле уже лет как 20 не пишут.
Мне вот даже интересно, откуда вы брали примеры кода? Что за книгу вы купили? Ведь даже идиот Робин Никсон в последних изданиях не пишет настолько чудовищные вещи. Если смотреть правде в глаза, то вас тут не размазали только потому, что никто не потрудился заглянуть в код. А учитывая, что в своей статье вы делали что угодно, только не разбирали этот кошмар, то ваше заявление про "чтобы никто не повторял" выглядит довольно жалко. Собственно, из ошибок вы упомянули только один косяк в структуре таблиц, из, минимум, десятка.
В целом, конечно, к теме "чтобы никто не повторял" это нелепое произведение не имеет никакого отношения. Скорее первая проба пера начинающего автора, такой лучащийся гордостью отчёт о проделанной работе, который годится разве что в школьную стенгазету, а на Хабр он попал по недоразумению.
Впрочем, не принимайте мои слова близко к сердцу (я уверен, что вы и так не принимаете, но всё равно чувствую себя обязанным это сказать). Хотя для Хабра образца хотя бы двухлетней давности эта статья - просто позор, для школьника это довольно сносное произведение. А тот факт, что вы его осилили говорит о том, что у вас есть потенциал и, возможно, со временем вы научитесь писать сносно - как код, так и тексты.
timursper Автор
04.02.2025 06:29Я старался в статьей рассказать очень кратко об этом проекте и разбирал те части, к которым у меня остались иллюстрации, чтобы у пользователя было правильное представление того, как выглядит сайт, поэтому я не стал каждую строчку разбирать. Если бы я и написал такую статью, то читать бы ее пришлось больше 2-х часов и никто, конечно же, до конца её не дочитает.
Почему бы мне просто не запустить проект у себя на компьютере? Дело в том, что на гитхабе самая актуальная версия системы.
FanatPHP
04.02.2025 06:29Мне кажется, вы не прочти мой комментарий.
Попробуйте прочитать его ещё раз, не торопясь. Подумайте - о чём он. Посмотрите, нет ли в нём вопросов, на которые вас попросили ответить. Задумайтесь над вопросом, нужно ли в хабе, посвящённом серверному программированию, описывать то, как выглядит ваш сайт. Или интересно ли профессиональным разработчикам, составляющим аудиторию Хабра, получать представление о первом, очень слабом проекте начинающего программиста. просто представьте себе свою потенциальную аудиторию. Кому в принципе адресована ваша статья?
timursper Автор
04.02.2025 06:29Статья адресована новичкам, которые тоже хотят делать проекты, связанные с образованием (или же для школ), чтобы они не допускали таких ошибок, как тут.
FanatPHP
04.02.2025 06:29Ошибку (разобранную) я вижу тут только одну, со структурой таблиц. При этом использованное решение объясняется почему-то не показом новой структуры таблиц... а картинками, показывающими интерфейс. Но при этом вижу другие ужасы со структурой таблиц и чудовищный код, на который я дал ссылку, и о котором вы вообще ни словом не заикнулись. То есть со всем остальным, по-вашему, всё в порядке? И вы предлагаете новичкам писать именно так:
if ($_COOKIE["acclogin"] == $login) { $isAuthor = true; }
Вот с этим детским лепетом вместо авторизации, когда достаточно подменить куку чтобы авторизоваться под кем угодно?
$publicid = $_GET["id"]; $res = $conn->query("SELECT * FROM `communities` WHERE `publicid` = '$publicid'");
С хрестоматийной инъекцией из комикса про мальчика Bobby Tables?
$res = $conn->query("SELECT `id` FROM `communityposts` WHERE `community` = '$publicid'"); $id = $res->num_rows+1;
с нелепым инкрементом, который сломан даже в рамках своей собственной кривой логики?
Вот вы сейчас это серьёзно, считаете что можно рекомендовать новичкам проект, написанный по стандартам прошлого века, причём с детскими ошибками, которые уже тогда считались позором?
SergeyNovak
Отлично, но вайбы как-будто я в 90е окунулся. Сейчас уже так не делают. На сервере выставляют API, а из странички с JS приложением обращаются уже к бэкэнду.
timursper Автор
Сейчас я так и делаю в проекте с использованием Django