Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#1 08.03.2020 13:56:15

Как заменить много use'ов одним или правильная организация структуры.

Здравствуйте!

Пишу на 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)

Не в сети

#2 08.03.2020 14:39:44

Re: Как заменить много use'ов одним или правильная организация структуры.

Тут однозначного ответа/совета быть не может - у каждого свое понимание ООП (а кто-то вообще против него, хоть таких в 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 все очень хорошо расписано:

Не в сети

Подвал раздела