Русское сообщество разработки на PHP-фреймворке Laravel.
Ты не вошёл. Вход тут.
Здравствуйте!
Пишу на laravel 5.2 собственного бота. Бот должен отвечать на запросы пользователей по командам на клавиатуре. Собственно, клавиатуру я представляю им тоже. За основу бота была скачена либа, предоставляющая фасад с классами упрощенного взаимодействия с ботом, а именно мне не приходится вручную формировать POST-запрос - либа делает это сама.
Команды к боту делятся на типы. Типы собраны в одну из нескольких клавиатур - каждая на свой тип. Эти клавиатуры объединяет главная клавиатура - меню.
Когда на сайт поступает сообщение с сервера телеграм, то после записи сообщения в базу, сообщение отправляется в главный файл бота - Core.php. Его код выглядит так:
<?php
namespace App\TelegramBot;
use Telegram;
use App\Http\Controllers\UserController;
use App\TelegramBot\MessageParser;
use App\TelegramBot\Commands\Команда1Command; //тип А
use App\TelegramBot\Commands\Команда2Command; //тип А
use App\TelegramBot\Commands\Команда3Command; //тип А
use App\TelegramBot\Commands\Команда4Command; //тип Б
use App\TelegramBot\Commands\Команда5Command; //тип Б
use App\TelegramBot\Commands\Команда6Command; //тип В
use App\TelegramBot\Commands\Команда7Command; //тип В
// + еще с десяток юзов
class Core
{
public static function main($message) {
if(!self::auth($message)): // Проверяем есть ли такой пользователь в базе. Нужно для того, чтоб 'левые' люди не имели доступа к
// пользованию ботом
return;
endif;
$result = MessageParser::parse($message); // Парсим регуляркой тип команды из текста сообщения. Это своего рода определитель команды
$action = false; // Создаем переменную, в которую поместим объект на исполнение команды
switch($result)
{
case 'Команда 1':
$action = new Команда1Command($message);
break;
case 'Команда 2':
$action = new Команда2Command($message);
break;
case 'Команда 3':
$action = new Команда3Command($message);
break;
// + еще с десяток команд
default:
$action = new КомандаНеОпределенаCommand($message);
}
if($action) $action->run();
}
На данный момент команд очень мало, но в перспективе их будет много. В тексте кода уже видно, что подгружать каждую команду вручную неудобно - слишком много юзов. Поэтому возникла идея реструктуризировать бота, пока он несложен.
Изначально любая команда или действие описывалась абстрактным классом Command и держалась в папке Commands/, но держать все типы команд в одной папке неудобно. Поэтому возникла идея усложнить структура, но сделать ее более читаемой и простой на расширение. А именно сделать так:
Создать абстрактный класс Action, описывающий любой тип команд к боту. Поместить этот класс в свою папку Actions/ - она будет содержать все типы действий и обращений к боту, требующих ответа. Внутри папки Actions/ создать несколько папок, определяющих свои команды. Каждая из команд будет наследоваться от Action.
На данный момент имею это:
TelegramBot/
|--> Core.php
|--> MessageReceiver.php
|--> MessageParser.php
|--> Commands/
|--> Command.php - абстрактный класс, описывающий команды. Представлю его ниже.
|--> КомандаACommand.php
|--> КомандаБCommand.php
|--> КомандаВCommand.php
|--> ...
Хочется иметь это:
TelegramBot/
|--> Core.php
|--> MessageReceiver.php
|--> MessageParser.php
|--> Actions/
|--> Action.php - тот же класс Command
|--> Keyboards/Команды на предоставление клавиатуры, описываемые классом Action
|--> Options/Команды опций, описываемые классом Action
|--> Reports/Команды на отчет, описываемые классом Action
|--> ...
<?php
namespace App\TelegramBot\Commands;
abstract class Command
{
private $message;
private $answer;
public function __construct($message)
{
//
}
public function run()
{
//
}
}
Проблема в том, повторюсь, что подгружать каждый файл в Core.php крайне неудобно. Моих знаний ларавеля, как и ООП, недостаточно, чтоб правильно реализовать данную задачу. Как я понимаю, необходимо создать что-то на подобии автолоадера, который будет автоматом подгружать каждый класс из каждой папки и переносить их в Core. Тут я уже не соображаю.
Вообще, интересна критика данной структуры. Я только начинаю более менее нормально кодить. Жажду прогресса.
Прошу помощи, ссылки, совета - приму все, что поможет. Надеюсь, изложил все внятно, но при необходимости дам разяснения.
Спасибо.
Изменено Remover_4000_pro (08.03.2020 13:58:43)
Не в сети
Тут однозначного ответа/совета быть не может - у каждого свое понимание ООП (а кто-то вообще против него, хоть таких в PHP не много). Так что ниже сугубо мое личное мнение.
Ты столкнулся с ситуацией, когда излишняя иерархия вредит. Есть две иерархии: плоская (по сути, отстутствие иерархии) или полная (предельная группировка по пространствам имен, как в твоем желаемом примере с App\TelegramBot\Actions\Keyboards\Xyz...).
Когда-то, в PHP до версии 5.3 пространств имен (namespaces) не было. Во многих ЯП их нет до сих пор (Си, JavaScript, Lua и пр.). В этом случае проблема коллизий (совпадений имен, когда у тебя есть класс Foo и у библиотеки тоже есть класс Foo) решалась путем префиксов и суффиксов. Например:
MyTelegramBotKeyboardAction
Когда появились NS и народ кинулся в другую крайность, получилось так:
My\Telegram\Bot\Action\Keyboard
Интересно заметить, что первый вариант гораздо ближе к естественному написанию, чем второй ("my telegram bot's keyboard action" <=> "my telegram bot, (its) action (of/for) keyboard").
А еще интереснее, что многие вообще начинают совмещать оба подхода, и вот это мне совсем не понятно:
My\Telegram\Bot\Action\KeyboardAction
Ясно, что так проще (короче) писать use, но это уже совсем масло-масляное ("ActionKeyboardAction") - может тогда это промежуточное пространство имен вообще не нужно?
Я лично сторонник полу-плоской иерархии. NS отлично выполняют задачу изолирования твоего кода от окружающего, но зачем идти дальше и изолировать твой же код сам от себя? Ты ведь и так не допустишь коллизий по именам, код-то твой собственный. В этом случае имена могут выглядеть так:
MyTelegramBot\KeyboardAction
То есть одно большое NS, где внутри все остальное, без под-NS. Такая структура позволяет избежать use практически целиком. Например, если у тебя есть класс App\TelegramBot\Commands\SomeCommand и ему надо вызвать метод у Core, то ты будешь писать так:
class SomeCommand {
function foo() {
\App\TelegramBot\Core::bar();
Или так:
use App\TelegramBot\Core;
class SomeCommand {
function foo() {
Core::bar();
Ни то, ни другое мне не нравится, так как нужно стремиться, чтобы в коде было как можно больше логики и как можно меньше поддерживающих конструкций (за что ругают ООП, когда сравнивают с процедурным стилем), а в данном виде это чисто конструкции ради конструкций.
А вот если у тебя и Command, и Core в одном NS - то никаких проблем:
class SomeCommand {
function foo() {
Core::bar();
Получаешь лучшее из двух миров - use не надо (ибо классы в одном NS), в то же время ссылаешься на класс, как будто бы use был.
Таким образом, use или пути остаются только для зависимостей (библиотек), но это неизбежное зло и обычно к классам из библиотек обращений на порядок меньше, чем к собственным, так что озвученная проблема особо не стоит.
Моих знаний ларавеля, как и ООП, недостаточно, чтоб правильно реализовать данную задачу. Как я понимаю, необходимо создать что-то на подобии автолоадера, который будет автоматом подгружать каждый класс из каждой папки и переносить их в Core.
Я думаю, тебе стоит почитать про ООП в PHP подробнее, потому что у тебя путаница: use не вызывает автозагрузчик и вообще никак не относится к нему. Автозагрузчик сам по себе. Когда ты пишешь use, ты просто создаешь псевдоним для отдельно взятого класса в рамках этого файла: вместо \Some\Class::foo ты можешь написать один раз use Some\Class и далее везде просто Class::foo.
В доке по PHP все очень хорошо расписано:
Не в сети