Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Клуб программистов > Свободное общение
Регистрация

Восстановить пароль

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 11.02.2014, 01:08   #21
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

Продолжаю развивать идею тотальной автоматизации. Но чем дальше тем сложнее проектировать модули и связи между ними.
На днях попробовал задумку связанную с обработчиком url'ов. Использовал регулярки и контролы, получилось круто, примерно так есть в прототипе:
PHP код:
$actions = [
    
/* -=-=-=-=-=-=-=-=-=- MAIN  =-=-=-=-=-=-=-=-=- */
    
['preg' => '/^$/''controller' => $guestbook'action' => 'render''params' => []],
    
/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
    
    /* -=-=-=-=-=-=-=-=-=- COMMENTS  =-=-=-=-=-=-=-=-=- */
    
['preg' => '/^(guestbook)[\/]*(insert)/''controller' => $guestbook'action' => 'insert''params' => []],
    [
'preg' => '/^(guestbook)[\/]*(get)/''controller' => $guestbook'action' => 'get''params' => []],
    [
'preg' => '/^(guestbook)[\/]*(update)/''controller' => $guestbook'action' => 'update''params' => []],
    [
'preg' => '/^(guestbook)[\/]*([0-9]+)/''controller' => $guestbook_ajax'action' => 'render''params' => ['''id']],
    [
'preg' => '/^(guestbook)[\/]*/''controller' => $guestbook'action' => 'render''params' => ['''id']],
    
    
/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */

    /* -=-=-=-=-=-=-=-=-=- 404  =-=-=-=-=-=-=-=-=- */
    
['preg' => '/^.*/''controller' => $page404'action' => 'render''params' => []],
    
/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
]; 
Для оптимизации подобной штуки, нужно реализовать что-то на подобии своих регулярок, чтобы поиск шел не подряд, а по дереву.

Попробовал один из способов для перевода интерфейса. Самый простой, создать пару каталогов для ru и en, где размещены переводы. Удобно, что если перевода для одного из полей нет, то php ругнется на отсутствующий ключ, но по мне, это не айс. Впоследствии реализовал так, что есть язык по умолчанию(en) который подгружается в любом случае, но если требуется другой, то погружается второй язык, заменяя собой часть(или полностью) язык по умолчанию.
Загвоздки появились с ajax, приходилось отсылать часть перевода вместе с данными из модели, пока думаю над этим.
Также еще не пробовал реализовать разделения контента по языкам, потому что бывает куча разных случаев. Во первых сайты на разных языках могут иметь различные сигнатуры, во вторых наоборот, страницы одни, а вот информация на них меняется в зависимости от языка(например справка по php).
Правда можно все это реализовать такими ссылками:
Код:
domain.www/ru/manual/ru/debugger
При этом первая ru указывает на интерфейс сайта и может быть опущена, т.к. будет сохранена в сессии. Вторая же ru указывает на поддерево разделов сайта, по мне такой подход просто идеален и решает кучу проблем. При определенной сноровки второй ru тоже можно будет опустить.
Первую ru разруливаю так:
Код:
<IfModule mod_rewrite.c>
    RewriteEngine on

    RewriteCond %{REQUEST_FILENAME} !\.(jpg|jpeg|gif|png|css|js|pl|txt|xml|html|mp4|flv|ogv|webm)$
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(en|ru)/(.*)$ index.php?lang=$1&url=$2 [L,QSA]

	RewriteCond %{REQUEST_FILENAME} !\.(jpg|jpeg|gif|png|css|js|pl|txt|xml|html|mp4|flv|ogv|webm)$
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
	RewriteRule ^(.*)$ index.php?url=$1 [L,QSA]
</IfModule>
Т.к. она не является частью дерева каталогов сайта. В общем шаблоны для url которые вытаскивают из них параметры решают все проблемы и их можно вертеть как угодно. Вот такое небольшое личное открытие =)

Этот "гениальный" стыд движок опубликовал на github https://github.com/kostia256/JustCMF
Kostia вне форума Ответить с цитированием
Старый 25.06.2014, 17:48   #22
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

По прежнему ищу помощников!

На сегодня я все переписываю уже на ПЯТЫЙ раз. Хочу поделиться реализацией отдельных частей, т.к. все вместе уже стало ооочень сложно обозреть.

Начну с роутинга. Суть заключается в 2 классах реализующих интерфейсы:
Код:
interface IRoute
{
	public function __construct($path, $action);
	public function match(IRequest $request);
	public function getParams();
	public function getAction();
}

interface IRouter
{
	public function get(IRoute $route);
	public function put(IRoute $route);
	public function delete(IRoute $route);
	public function post(IRoute $route);
	public function head(IRoute $route);
	public function options(IRoute $route);
	/**
	 * 
	 * @param IRequest $request
	 * @param IResponse $response
	 * @return IRoute
	 */
	public function routing(IRequest $request, IResponse $response);
}
Работает все просто:

Код:
$router->get(new Route($pattern, $action));
Сейчас реализация IRoute работает с регулярными выражениями. И поддерживается следующее задание шаблонов:
Код:
/blog/:tag/:id
/blog/{:tag|(\w+)}/{:id|(\d+)}
что соответствует:
Код:
/blog/(.*)/(.*)
/blog/(\w+)/(\d+)
реализация:

Код:
class Route implements IRoute
{

	protected $action;
	protected $pattern;
	protected $params;
	protected $names;

	public function __construct($pattern, $action)
	{
		$this->action = $action;
		$this->pattern = $pattern;
		$this->names = [];
		$this->params = [];
		
		$preg = "/{:([a-zA-Z0-9]+)\|\(([a-zA-Z\\\+\*\-\.]+)\)}/i";
		preg_match_all($preg, $this->pattern, $params);
		$this->names = array_merge($this->names, $params[1]);
		$this->pattern = preg_replace($preg, "($2)", $this->pattern);
		
		$preg = "/:([a-zA-Z0-9]+)/i";
		preg_match_all($preg, $this->pattern, $params);
		$this->names = array_merge($this->names, $params[1]);
		$this->pattern = preg_replace($preg, "(.*)", $this->pattern);
		
		$this->pattern = '/^' . str_replace('/', '\/', $this->pattern) . '$/';
	}

	public function match(IRequest $request)
	{
		$params = [];
		if (preg_match($this->pattern, urldecode($request->getPathInfo()), $params))
		{
			array_shift($params);
			foreach ($params as $key => $value)
			{
				$this->params[$this->names[$key]] = $value;
			}
			return true;
		}
		return false;
	}
	
	public function getParams()
	{
		return $this->params;
	}

	public function getAction()
	{
		return $this->action;
	}

}
соответственно, если перейти по ссылке
/blog/php/1
в параметрах Route будет массив ["tag" => "php", "id" => "1"]
но
/blog/php/article
уже не пройдет, т.к. article не число

Предвижу баги с
Код:
$preg = "/{:([a-zA-Z0-9]+)\|\(([a-zA-Z\\\+\*\-\.]+)\)}/i";
Kostia вне форума Ответить с цитированием
Старый 25.06.2014, 18:27   #23
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

Следующий элемент. Также очень интересный, но еще не завершенный. Это класс Action. Его главная роль в валидации и преобразовании данных с помощью отображений.
Код:
$this->makeRoute("get", "/{:param|(.*)}", "pages.main", $this->wrapMethod("main"))
				->param(new \System\Action\Types\Any("param"), [new \System\Action\Mappings\email(new \System\Action\Types\Any("param"))]);
Т.к. Action еще находится в разработке, нет фабрик типов и преобразование, приходится создавать классы напрямую для тестов. Суть того что в этой строчке творится:

Код:
	protected function makeRoute($method, $pattern, $name, $function)
	{
		$action = $this->services->actions()->action($name, $function);
		$this->services->router->$method($this->services->router()->route($pattern, $action));
		return $action;
	}
	
	protected function wrapMethod($method)
	{
		return function() use ($method) {return call_user_func_array([$this, $method], func_get_args());};
	}

	protected function main($param)
	{
		$this->services->response()->setBody($param);
	}
Если перейти по ссылке:
/hunter@hunter.com
то на экране напечатается:
hunter@hunter.com
Но если параметр не является валидным email, то будет исключение от action, что отображение email прошло с ошибкой. Практически того же эффекта можно было добиться специфическим типом Email, при поппытк установить ему значение было бы сгенерировано исключение. Например:
Код:
$this->makeRoute("get", "/{:param|(.*)}", "pages.main", $this->wrapMethod("main"))
				->param(new \System\Action\Types\Integer("param"));
В то время как значения получаемые из пути можно проверить с помощью регулярных выражений, то параметры передаваемые через get или post проверяются уже в action.
Важно что можно реализовать сколь угодно сложные преобразования типов. Например можно реализовать отображение file, которое будет сохранять загруженный файл в указанную директорию и возвращать имя файла в виде строки которая будет записана в модель. Тоже для картинок с возможностью создавать preview.

С типами есть еще одна интересная возможность. Можно подписаться на изменение значения. Например:
Код:
class Observer implements \SplObserver
{
	public function update(\SplSubject $subject)
	{
		echo array_sum($subject->getValue()) . " ";
	}
}

$series = new System\Action\Types\ArrayOf(null, [], "Integer");
$series->attach(new Observer());

$series->setValue([1,2,3,4,5]);
$series[3] = 2;
unset($series[2]);
На экране будет:
15 13 10
Суть в том, что при добавлении, удалении или изменении элемента массив будет вызвано событие update у всех подписавшихся. Таким образом можно реализовать сложную логику в моделях, когда поля являются результатом функции от других полей.
Kostia вне форума Ответить с цитированием
Старый 03.07.2014, 03:08   #24
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

Доброго времени суток.

Из предыдущего поста:
Цитата:
С типами есть еще одна интересная возможность. Можно подписаться на изменение значения. Например:
Выпилено из-за избыточности. Т.к. сложную логику полей моделей можно в самих моделях организовать.

Я предпочитаю писать общие решения, частным. Если какое либо поведение может встречаться чаще одного раза, то его лучше вынести в класс расширение. А теперь о моделях =)

Любая модель это экземпляр класса AbstractEntity или его наследники или любой другой реализующий интерфейс IAbstractEntity.

Код:
interface IAbstractEntity extends \ArrayAccess, \Countable, \IteratorAggregate
{
	public static function fromTable(\System\Database\Table $table, $mapper = null);
	public function setMapper($mapper);
	public function getMapper();
	public function findById($id);
	public function save();
	public function delete();
	public function set(array $row);
	public function toArray();
}
Статическая функция fromTable является вспомогательной для создания прототипа модели по описанию таблицы. Прототип требуется для работы фабрики моделей и коллекций моделей.
Код:
$newsCategories = new Table("news_categories", [
	Type::String("title")
]);

$newsItems = new Table("news_items", [
	Type::String("title"),
	Type::Text("short"),
	Type::Text("full"),
	Type::Integer("news_categories_id")
]);

$this->services->dataManager()->addAbstractDataMapper($newsCategories);
$this->services->dataManager()->addAbstractDataMapper($newsItems);
Код:
public function addAbstractDataMapper(\System\Database\Table $table)
{
	$this->addDataMapper(new AbstractDataMapper($this->services->db(), $table, 
			new AbstractEntityFactory(AbstractEntity::fromTable($table))));
}
Сейчас в таблицах нельзя описать внешние ключи и задавать отношения между таблицами, а также добавлять различные расширения типа: created, edited, showorder ... Как раз над этим сейчас работаю.

Типы полей таблицы отображаются в типы движка автоматически, но можно задать свои типы для моделей. Соответственно модель с неправильными данными не будет создана. Ошибки можно перехватить и обработать. Правда контроль качества данных полученных из БД планируется убрать из-за ненадобности.

Соответственно можно легко наплодить множество одинаковых экземпляров моделей и заполнить ими базу:
Код:
for($i=0; $i<1000; $i++)
{
	$this->pageMapper->getFactory()->createEntity(['title' => "Еще одна страница"])->save();
}
Редактировать и клонировать:
Код:
//редактировать
$page = $this->pageMapper->findById(2);
$page->description = "Скучная страница, уходим отсюда!";
$page->save();
//клонировать
$page_clone = clone $page;
$page_clone->save();
Также радует работа с коллекциями моделей и шаблонизаторами, не приходится заморачиваться с передачей во view и делается одной строчкой:
Код:
$this->services->view()["data_manager"]["rows"] = $this->dataMappers[$data]->findAll();
Также если нужно создать таблицу в базе по ее описанию можно сделать так:
Код:
$this->services->db()->alterTable($pagesTable);
В случае отсутствия таблицы она будет создана, а в случае разности структур, то таблица в базе будет подогнана под переданную. Это очень удобно в процессе разработки, когда постоянно приходится добавлять новые таблицы и менять уже имеющиеся. Также в будущем планируется это использовать при установки модулей.

На пятый раз я хотел бы дойти до конца и хотя бы повторить возможности первой версии, но уже организованной значительно грамотнее.

Начал понемногу намечать админку.
Изображения
Тип файла: jpg снимок23.jpg (18.4 Кб, 56 просмотров)
Kostia вне форума Ответить с цитированием
Старый 11.07.2014, 23:09   #25
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

Я очень счастлив, что наконец знаю где должна лежать загрузка и обработка файлов =)

Нахожусь несколько в бредовом состоянии после работы, но хочу поделиться наработками.

Начну с того что я неадекватный раз принялся за реализацию подобного конструктора. Реализовал ~60% от MySQL\Select, чуть-чуть MySQL\WhereExpr и остальные мелочи.

Вот это:
PHP код:
echo (new Select())
        ->
expr(new SelectFields(["id""keywords""link""title"], "jcmf_pages"))
        ->
expr(new SelectFields(["title" => "news_category"], "jcmf_news_categories"))
        ->
from((new TablJoin(new TableName("jcmf_pages")))
                ->
join(new TableName("jcmf_news_categories"))
                ->
on((new Where())->cond(new WhereField("id""jcmf_pages"), "=", new WhereField("page_id""jcmf_news_categories")))
        )
        ->
where((new Where())
                ->
cond((new Where())->cond(new WhereField("id""jcmf_pages"), ">", new WhereConst("490")), "or", (new Where())->cond(new WhereField("id""jcmf_pages"), "=", new WhereConst("485")))
                ->
in(new WhereField("id""jcmf_pages"), (new TableSubquerySet())
                        ->
expr(new SelectField("id""""jcmf_pages"))
                        ->
from((new TableSubquery("x"))
                                ->
expr(new SelectField("id""""jcmf_pages"))
                                ->
from(new TableName("jcmf_pages"))
                                ->
where((new Where())->cond(new WhereField("id""jcmf_pages"), "<", new WhereConst("490")))
                        )
                )
        )
        ->
limit(new Limit(5)); 
Превращается в это:
Цитата:
SELECT `jcmf_pages`.`id`, `jcmf_pages`.`keywords`, `jcmf_pages`.`link`, `jcmf_pages`.`title`, `jcmf_news_categories`.`title` AS `news_category` FROM `jcmf_pages` JOIN (`jcmf_news_categories`) ON (`jcmf_pages`.`id` = `jcmf_news_categories`.`page_id`) WHERE ((`jcmf_pages`.`id` > '490') or (`jcmf_pages`.`id` = '485')) AND (`jcmf_pages`.`id` IN (SELECT `jcmf_pages`.`id` FROM (SELECT `jcmf_pages`.`id` FROM `jcmf_pages` WHERE (`jcmf_pages`.`id` < '490')) AS `x`)) LIMIT 5
Прелесть 1: Проверка валидности запроса еще на этапе его построения. Например при выборки поля из подзапроса в котором его нет, очевидная ошибка и т.д.
Прелесть 2: Контекст построения запроса может гулять по коду до полного своего формирования и только после этого выполниться. Очень удобно для расширений, DataMapper'ов, фабрик и т.д.
Прелесть 3: Прелесть 1 и Прелесть 2

_________________
Мне очень полюбился REST и я начал им злоупотреблять. Благо можно сделать заглушку и получать самые разные методы отправки запросов GET, POST, PATCH, CLONE, PUT, DELETE, HEAD, OPTIONS ...
Например удаление ресурса:
PHP код:
<a href="{{root}}DataManager/{{data_manager.structure}}/{{data_manager.data}}/{{row.id}}" class="delete-row" rel="popover" data-placement="top" data-content="{{"delete"|l10n('CPanel')}}">
    <
span class="glyphicon glyphicon-trash info"></span>
</
a
Код:
	$(".delete-row").on("click", function() {
		var self = $(this);
		$.ajax({
			type: "DELETE",
			url: self.attr("href"),
			success: function(data){
				self.parent().parent().remove();
			}
		});
		return false;
	});
Код:
	protected function delete($structure, $data, $id)
	{
		$entity = $this->offsetGet($structure)->getMappers()[$data]->findById($id);
		if ($entity)
		{
			$entity->delete();
		}
		else
		{
			//error
		}
	}
Очевидно что необходимо введение NullEntity, чтобы сократить код до:
Код:
$this->offsetGet($structure)->getMappers()[$data]->findById($id)->delete();
Соответственно если в базе нет такой записи, то mapper вернет NullEntity(NullUser, NullComment, NullItem ...) и при попытке вызвать save, delete или выполнить присвоение, то будет сформировано соответствующее исключение.

Суть имея ссылку на ресурс: httр://domain/DataManager/Pages/pages/485, то посредством разных методов запросов можно либо удалить данный ресурс(DELETE), клонировать(CLONE), получить список доступных операций(OPTIONS), обновить(POST), применить частичное обновление(PATCH) и т.д.
Имея: httр://domain/DataManager/Pages/pages/, то можно создать ресурс(PUT), получить список всех ресурсов(GET) и т.д. Через дополнительные параметры запроса можно определить поведение, например фильтр, порядок и количество.

Конечно следовать fullREST невозможно, но оно и не нужно.

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

Код:
class News extends Structure
{

	protected function initialize()
	{
		$this->tables["Categories"] = new Table("news_categories", [
			Type::String("title")
		]);

		$this->tables["Items"] = new Table("news_items", [
			Type::String("title"),
			Type::Text("short"),
			Type::Text("full"),
			Type::Integer("news_categories_id")
		]);

		foreach ($this->tables as $key => $table)
		{
			$this->mappers[$key] = $this->makeAbstractEntityMapper($table);
		}
	}

}
Как мне это поможет в будущем я не знаю, положусь на интуицию =)
уф
Kostia вне форума Ответить с цитированием
Старый 11.07.2014, 23:10   #26
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

Задачи на выходные:
1. Сделать PUT(insert), POST(update) ресурсов.
1.1 Сделать OPTIONS для получения информации о возможных действиях и описании самих действий для автоматизации построения форм и валидации на стороне клиента.
1.1.1 Придумать куда пихать mapping'и(require, email, is_integer ...) для автоматизации построения действий над ресурсами. Точенее это уже автоматизировано, но без отображений.
2. Начать работу над расширениями таблиц
3. Пилить конструктор запросов

Другие задачи:
1. Реализовать сервис для работы с сессиями. Долой session_start() и даёшь $this->response->cookies["sid"] = ["value" => bin2hex(openssl_random_pseudo_bytes (16)), "expires" => time() + 3600];
2. Прикрутить lessphp
3. Реализовать сервис для ведения статистики
4. Реализовать сервис для бекапов
5. Добавить пользователей и систему управления правами доступа
6. Доделать Route, не допускать роутов перекрывающих другие и наоборот не допускать роутов которые будут перекрыты.
7. Реализовать сервис логирования и обработки ошибок
Kostia вне форума Ответить с цитированием
Старый 12.07.2014, 00:02   #27
Arigato
Высокая репутация
СуперМодератор
 
Аватар для Arigato
 
Регистрация: 27.07.2008
Сообщений: 15,878
По умолчанию

Неплохо для тренировки.
Arigato на форуме Ответить с цитированием
Старый 12.07.2014, 01:41   #28
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

Цитата:
Сообщение от Arigato
Неплохо для тренировки.
Собственно я на этом и учусь

Еще по поводу REST и разницы между POST и PATCH хочу высказаться, их разница в следующем:

POST:
Код:
$user = $UserMapper->createEntity($_POST); //создание новой сущности
$user->save();
PATCH:
Код:
$user = $UserMapper->findById($_POST["id"]); //получение сущности из базы
$user->set($_POST); //изменение некоторых полей
$user->save();
Как видно в случае с POST, сущность целиком воссоздается из пришедших данных и заменяет собой ту, что в базе. А в случае PATCH, из базы выбирается соответствующая сущность и ей устанавливаются только те поля которые пришли.

Имея
Код:
id name  surname
1  petya petrov
И послав {id: 1, name: "vasya"} методом POST получим:
Код:
id name  surname
1  vasya
PATCH:
Код:
id name  surname
1  vasya petrov
Также фундаментальная разница в том, что если записи с таким id нет, то при POST запросе она будет создана, а при PATCH получим исключение, т.к. $UserMapper->findById($_POST["id"]) вернет NullUser который сгенерирует исключение при попытке установить ему значение полей. Соответственно даже если при PATCH запросе будут переданы все поля описывающие сущность это не будет является тем же что и POST.

Последний раз редактировалось Kostia; 12.07.2014 в 01:45.
Kostia вне форума Ответить с цитированием
Старый 27.07.2014, 21:02   #29
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

Вникая все глубже и глубже в дебри проектирования, немного перестроил движок. Изначально было выбран неплохой подход, раз смог безболезненно кардинально перестроить большую часть системы.

Приложение располагается в %document root%/Application/

Я всё никак не мог формализовать понятие страницы, пока не пришло самое простое и очевидное определение. Страница - это совокупность компонентов. Т.е. это не какая-то особая сущность, а обыкновенный контейнер для компонентов.

Наконец можно определять отношения между таблицами. Определил отношение один к одному и частично один ко многим. Над многие ко многим еще думаю. + Нужно кучу оптимизаций провести, иначе 500+ запросов к базе для одной страницы гарантированны =)

Для тестов было создано 2 страницы. На одной из которых расположены компоненты, а вторая для тестов по разрешению таких ситуаций когда возникает рекурсия в отношениях. Например если эти страницы являются друг другу родителями, то получаем рекурсию и т.д.

Также был создан простой контроллер:

Код:
abstract class Controller implements \System\Interfaces\IController
{

	/**
	 *
	 * @var Registry
	 */
	protected $registry;

	final public function __construct()
	{
		
	}

	static public function run()
	{
		$instance = new static();
		$instance->initialize();
		$instance->handleRequest();
	}

	protected function initialize()
	{
		$this->registry = Registry::getInstance();
	}

	protected function handleRequest()
	{
		$cmd_r = new \Application\Command\CommandResolver($this->registry);
		$cmd = $cmd_r->getCommand();
		$cmd->execute();
		if ($cmd->getStatus() != 1)
		{
			echo "ERROR";
		}
	}
}
CommandResolver решает какую команду нужно создать. Таким образом controller для страниц выглядит неприлично:

Код:
class PageController extends Controller
{
	public function handleRequest()
	{
		parent::handleRequest();
	}
}
Для этого контроллер определено действие по умолчанию:

Код:
class DefaultCommand extends Command
{
	public function doExecute()
	{
		$page = \Application\Structure\Page::getInstance()->getMappers()["page"]->findById(1);
		
		$this->registry->view->setPath("Components");
		$this->registry->view["page"] = $page;
		
		$this->registry->response->setBody((string) $page->component);
		
		$this->setStatus("OK");
	}

}
Это действие получает страницу с id=1, настраивает путь для шаблонизатора, передает в него страницу и устанавливает тело ответа согласно определению страницы(совокупность компонентов).

У компонентов определен метод render() и __toString(). Они эквивалентны и это очен удобно.

Благодаря определенным связям между таблицами можно выполнить автоматическое построение моделей таким образом, что они тащат за собой всё дере зависимых элементов. Например выбрав страницу также выберутся все компоненты на ней.(Тут нужна оптимизация в виде отложенных запросов, т.е. получать данные из базы и выполнять построение сущностей только после первого обращения к ним). В общем если будет некая корневая страница root, а все остальные являются ее потомками, то при выборе root будет выбрана вся база целиком. Это очень удобно, особенно если хорошо оптимизировать.

Вся бизнес логика(магия) скрыта в моделях, фабриках и мапперах как и положено.

Компоненты делятся на 3 основные сущности:
Цитата:
Component extends AbstractEntity
ComponentDecorator extends Component
ComponentComposite extends Component
Т.е. декоратор также является компонентом, поэтому декоратор может декорировать другой декоратор. Это позволяет реализовать декоратор для администрирования. Если пользователь админ, то все компоненты заворачиваются в этот декоратор.

На скрине сгенерированная страница. Здесь
Цитата:
Any text on page: index
Этот компонент декорирован декоратором который задал ему цвет #EBF8A4.

Начинаю клепать редактор страниц. Вдохновился вот этой штукой: http://www.layoutit.com/
Изображения
Тип файла: png снимок1.png (3.2 Кб, 31 просмотров)
Тип файла: jpg Скриншот сделанный 2014-07-27 в 23.28.44.jpg (6.9 Кб, 105 просмотров)

Последний раз редактировалось Kostia; 27.07.2014 в 23:02.
Kostia вне форума Ответить с цитированием
Старый 19.11.2014, 18:27   #30
Kostia
Участник клуба
 
Аватар для Kostia
 
Регистрация: 21.11.2007
Сообщений: 1,692
По умолчанию

Кто-нибудь, откликнитесь на зов! =)

Вся система превратилась в хитрозакрученную спагетти. Здравомыслие меня уже давно покинуло и я долгими часа думаю над казалось бы простыми задачами.

1. Страницы как совокупность компонентов.

Чуть меньше месяца я занимаюсь версткой одного сайта, и в целях прокачки делаю дизайн адаптивным под мобилки, хотя изначально он нарисован ни разу не адаптивным, приходится импровизировать и согласовывать различные решения. Сегодня прочел статью на хабре, про то что google будет помечать адаптированные под мобильные устройства сайты и повышать их в выдаче в поиске на мобильных устройствах, а также состряпали утилиту для проверки сайта. Когда после проверки моей верстки я получил зеленый свет, то был на седьмом небе от счастья. Усилия не прошли даром =)

У любой страницы есть макет(сетка) на которую ложатся компоненты. Но существуют компоненты, которые присутствуют на каждой странице, например меню и футер. Эти компоненты можно заранее зафиксировать на макете. Но также бывает необходимо чтобы скажем на страницах новостей также были некоторые зафиксированы компоненты и на странице товаров и в статьях. Отсюда следует что необходимо наследование макетов. В таком случае почему бы просто не сделать наследование страниц.

Для удобство я завел компонент html и одну системную страницу от которой будут наследоваться все остальные. Таким образом можно реализовать последовательность наследующихся скелетов.

Например так:
html -> mash -> pageWithMenuAndFooter -> concretPage

У компонентов есть слоты в которые можно вставлять другие компоненты. Благодаря слотам можно реализовать компоненты для построения сетки.

Код:
	public function render(array $pages = [])
	{
		if($this->skeleton)
		{
			$pages[] = $this->id;
			return $this->skeleton->render($pages);
		}
		
		$result = "";
		foreach ($this->components as $component)
		{
			$result .= $component->render($pages);
		}
		
		return $result;
	}
Обязательно нужно знать всю цепочку скелетов, иначе скелету будет не понять из какой страницы брать компоненты для рендеринга и он возьмет все что есть, со всех страниц которые наследовали его =)

Про спагетти. Совсем недавно я битый час пытался понять что делает этот код, и как оно вообще работает:

Код:
class ComponentEntity extends \System\Entity\Entity
{
	public function render(array $pages = [])
	{
		return $this->{$this->component}->render($pages);
	}
}
Оказалось всё просто. Есть некая базовая модель Component, именно они размещены на странице. Также существуют конкретные реализации компонентов Html, Row ..., и они находятся в отношении один к одному с базовым Component, базовая модель компонента содержит имя конкретного компонента в поле component. Таким образом:

$this->{$this->component}->render()

Подгружает конкретную модель компонента и вызывает у нее render.

Это было сделано потому, что у каждого компонента могут быть свой набор параметров. Но это не главное, т.к. конкретные компоненты также являются моделями, то можно задать отношения между компонентами и другими моделями. Таким образом я получаю разделение между логикой работы конкретной модели и сбором данных для рендеринга. Например для модели News я могу создать 3 компонента NewsShow, NewsAnnonce и NewsArchive и описать получение данных в каждом из компонентов в зависимости от параметров самих компонентов, например в анонсе нужно показывать 6 последних новостей с тегами "Железо" и "Софт".
Kostia вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Что сейчас актуально, движки, CMF и так далее. dertsb WordPress и другие CMS 1 12.12.2013 07:25
«Сайт под ключ» на cmf Drupal manachud Фриланс 0 14.09.2011 21:00
Буду делать Аркаду 2D, ищу помощников CyberOrcX Gamedev - cоздание игр: Unity, OpenGL, DirectX 46 17.06.2009 09:48
Набираем помощников. Игра SanCIty microran Gamedev - cоздание игр: Unity, OpenGL, DirectX 1 11.09.2007 19:42
Набираем помощников. Игра SanCIty microran Gamedev - cоздание игр: Unity, OpenGL, DirectX 0 31.08.2007 17:45