Pull to refresh

PHP: Расширенный текучий интерфейс

Reading time 5 min
Views 3.7K
Уверен, что многим из вас, кто читает этот текст, знакомо понятие Текучий интерфейс. И даже если вы про него не слышали, уверен, что вы им пользовались и не раз. Это действительно удобно. Так о чем же идет речь?

<?php 

class Images {
	
	public $width;
	
	public $height;
	
	public function SetWidth($value) {
		$this->width = $with;
		return $this;
	}
	
	public function SetHeight($value) {
		$this->height = $value;
		return $this;
	}
	
}

$images = new Images();
$images->SetWidth(100)->SetHeight(100);

?>


Вот небольшой пример. Мы можем в строчку, последовательно, производить действия. Этот же принцип лежит в основе популярной библиотеки jQuery. Да что тут говорить, все современные фреймворки изобилуют подобными конструкциями. А вот что если использовать подобный механизм для построения всего сайта?


Вход


Тут я сделаю не большое отступление, вернусь назад. Чтобы продолжить нужно ответить на вопрос зачем? Ну, действительно, зачем придумывать велосипед, если есть стандартные модели и проверенные архитектуры. Я говорю про семейство моделей MVC. Чем MVC плох?

Да нет, он не плох. Но он имеет свои ограничения, как и все в этом мире. Это прекрасная модель построения сайта. Именно сайта. Но вот когда речь заходит о построении, на его основе веб-приложения, увы, ощущаются его ограничения. Давайте подробней рассмотрим ситуацию. Возьмем, например стандартный сайт. Не важно, какой он будет темы. Смотрим картинки.

image

Тут нарисована схема компоновки сайта. Мы видим блок новостей, вкладки (могут быть и просто кнопки), блок рекламы и блок содержание. Содержание содержит таблицу — индекс. Как организовать запрос к этой странице?

mysite.ru/section=2&task=index


Это классический, распространенный механизм. Один параметр передает раздел, другой задачу. Вот еще одна картинка. Смотрим

image

Тут уже содержание сайта содержит форму редактирование. Вот запрос к этой странице сайта

mysite.ru/section=2&task=edit


Все хорошо и понятно. Программируется такое тоже просто. Например, так

<?php

class Site {
	
	public function Section($value) {
		return new MyController();
	}
	
}

class MyController extends GeneralController {
	
	public function Index() {
		return new Table();
	}
	
	public function Edit() {
		return new Form();
	}
	
}

$site = new Site();
$section = $_GET["section"];
$task = $_GET["task"];

$site->Section($section)->$task();

?>


Класс Site возвращает контролер для нужного раздела. Контролер раздела MyController выполняет необходимые действия.

Скажите, а что делать, если, вы разрабатывает не сайт, а веб-приложение. Интерфейс веб-приложения на много сложней приведенного выше. Вкладки, таблицы, меню и т.д. Ведь до каждого элемента страницы мы должны иметь доступ, обновлять его, перерисовывать? Что если большая часть содержимого должна обновляться AJAX`ом? Смотрим еще картинку

image

Теперь наш сайт имеет дерево, кучу вкладок, форму, несколько тулбаров и т.д. Таблицы, что вставлены во вкладки, сортируются во все направления. А поле Аватар в форме может без перезагрузки всей страницы загружать и удалять картинку. Так как же организовать запросы ко всем этим элементам?

Давайте попробуем пойти тем же путем. Пусть у нас та же схема MVC. Очевидно, что нам понадобится еще один параметр который будет указывать что конкретно на странице изменить. Пусть он будет называться part. Тогда запросы могут выглядеть примерно так:

mysite.ru?section=2&part=tree&task=expand
mysite.ru?section=2&part=form&task=delete


А как обратится к тулбару в разделе Дерево? Или к тулбару во вкладке Содержания? Еще один параметр вводить? Тупик

Выход



И опять картинка.

image

Это структурная схема раздела Пользователи Электронного Университета. Я уверен, что не нужно иметь даже высшее образование, чтобы понять ее содержание. Понять, что Студенты состоят из Груп, а группы уже из элементов Студент. Перед нами вложенность. И теперь мы вспоминаем Текучий интерфейс и делаем следующее предположение. А почему бы методам корневого объекта не возвращать другие объекты, а ни только свой объект? Собственно, как только мы такое реализуем, мы легко развернем эту схему в цепочку. Поехали!

<?php 

class Members {
	
	public function Teachers() {
		return new Teachers();
	}
	
	public function Students() {
		return new Students();
	}	
	
}


class Teachers {

	public function Teacher($number) {
		return new Teacher($number);
	}
	
}


class Students {
	
	public function Group($number) {
		return new Group($number);
	}
	
}

class Group {
	
	public function Student($number) {
		return new Student($number);
	}
}


class Teacher {
	
	public function Show() {
		
	}
	
}


class Student {
	
	public function Show() {
		
	}
	
}

?>



Теперь, чтобы показать студента, нужно выполнить следующую цепочку команд

Members::Students()->Group(123)->Student(327)->Show();


Давайте повторим вопрос, вопрос который привел нас к тупику в предыдущем абзаце. А как обратится к тулбару в разделе Дерево?

Отвечаем. Вот так

Members::Students()->Group(123)->Student(327)->Tree()->Toolbar()->Add();


Сам класс Student может иметь внутри себя такие же вызовы на цепочки к другим объектом. Проще говоря, класс студент состоит из формы, метод Form(), Tree() — дерево и что там еще нужно. Это как конструктор Лего. Что видим, то и лепим, составляем, конструируем!

Десерт



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

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]


То все запросы на сайт, будут перенаправлены на index.php. В нем, вам останется ссылки типа

mysite.ru/control/members/students/group=123/student=321/show


превратить в цепочку команд

Application::Control()->Members()->Students()->Group(123)->Student(431)->Show();


Сделать это можно так.

<?php 

class Application {

	public function __construct() {
		if (!$this->request = $this->Request()) {
			$this->errors[] = ERROR_PARAM_GET;
		}		
	}
	
	public function Init() {
		if (!is_object(self::$instance)) {
			self::$instance = new self();
		}
		return self::$instance;
	}	
	
	public function Request() {
		if (!$uri = explode("/", $_SERVER["REQUEST_URI"])) {
			return false;
		} else {
			foreach ($uri as $item) {
				if (strpos($item, "=")) {
					$key = substr($item, 0, strpos($item, "="));
					$value = substr($item, strpos($item, "=")+1);
				} else {
					$key = $item;
					$value = null;
				}
				if (!empty($item)) {
					$result[] = array(
						"key" => $key,
						"value" => isset($value) ? $value : null
					);
				}
			}
		}
		return $result;		
	}
	
	function Run() {
		if (isset($this->errors)) {
			foreach ($this->errors as $error) {
				echo $error;
			}
		} else {
			$root = $this;
			/**
			 * Запускаю цепочку команд
			 * если команда не верная, возвращаю преветствие
			 */
			foreach ($this->request as $element) {
				if (is_object($root) && method_exists($root, $element["key"])) {
					$root = $root->{$element["key"]}(@$element["value"]);
				} else {
					return $this->Welcome()->Show();
				}
			}
		}
	}	
	
}

Application::Init()->Run();
	
	
?>


PS: Уважаемые коллеги. Приведенные примеры кода, не содержат проверок и по этому, не могут быть использованы в рабочих проектах как есть. Написаны они исключительно для наглядности.

Ссылки


Для подготовки этого материала, я использовал gomockingbird.com/mockingbird в котором я рисовал схемы сайта.

Tags:
Hubs:
+23
Comments 36
Comments Comments 36

Articles